[comp.sources.misc] v17i042: lwho - Low-cost rwho/ruptime-like programs, Part01/01

rsalz@bbn.com (Rich Salz) (03/17/91)

Submitted-by: Rich Salz <rsalz@bbn.com>
Posting-number: Volume 17, Issue 42
Archive-name: lwho/part01

We don't want to run RWHOD for all the standard reasons, but the
information that RWHO and RUPTIME provide is nice to have, so I wrote
LWHOD/LWHO, the "low-cost rwho package."

All machines have to be able to NFS-mount the same spool directory.  A
small daemon, LWHOD, on each machine writes a text file containing
statistics like boot-time, load average, users, etc.  The file is
basically self-describing (see lwhod.c), a decision which made this whole
thing pretty trivial to do.  Thanks to Jon Bentley and his "Programming
Pearls" books.

The user program, LWHO, reads the files and prints what the user has
asked for, in a useful way.  It does pretty much everything what rwho and
ruptime would do.  LWHO reads all the files into memory, so if
you run this on hundreds of systems that support hundreds of users,
you might lose.

I'd call this package a hack, but the ratio of code/functionality is so
high that it doesn't seem right.

To install, edit the Makefile to get the "install" stuff right for your
system.  Then edit lwho.h if you want to change the spool directory.  When
installing a daemon on a host make sure that SPOOLDIR is appropriately
NFS-mounted (and symlink'd) on the host.

I posted this to alt.sources a year ago.  Might as well give it wider
distribution.

Enjoy!
	/rich $alz; <rsalz@bbn.com>
-------
#! /bin/sh
# This is a shell archive.  Remove anything before this line, then feed it
# into a shell via "sh file" or similar.  To overwrite existing files,
# type "sh file -c".
# The tool that generated this appeared in the comp.sources.unix newsgroup;
# send mail to comp-sources-unix@uunet.uu.net if you want that tool.
# Contents:  README MANIFEST Makefile luptime.1 lwho.1 lwho.c lwho.h
#   lwhod.8 lwhod.c lwholib.c
# Wrapped by rsalz@litchi.bbn.com on Sat Mar 16 18:40:19 1991
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
echo If this archive is complete, you will see the following message:
echo '          "shar: End of archive 1 (of 1)."'
if test -f 'README' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'README'\"
else
  echo shar: Extracting \"'README'\" \(1376 characters\)
  sed "s/^X//" >'README' <<'END_OF_FILE'
XLow-cost Remote Who
X$Header: /nfs/papaya/source/lwho/RCS/README,v 1.2 91/03/16 18:09:36 rsalz Exp $
XThere is no copyright on this package.
X
XWe don't want to run RWHOD for all the standard reasons, but the
Xinformation that RWHO and RUPTIME provide is nice to have, so I wrote
XLWHOD/LWHO, the "low-cost rwho package."
X
XAll machines have to be able to NFS-mount the same spool directory.  A
Xsmall daemon, LWHOD, on each machine writes a text file containing
Xstatistics like boot-time, load average, users, etc.  The file is
Xbasically self-describing (see lwhod.c), a decision which made this whole
Xthing pretty trivial to do.  Thanks to Jon Bentley and his "Programming
XPearls" books.
X
XThe user program, LWHO, reads the files and prints what the user has
Xasked for, in a useful way.  It does pretty much everything what rwho and
Xruptime would do.  LWHO reads all the files into memory, so if
Xyou run this on hundreds of systems that support hundreds of users,
Xyou might lose.
X
XI'd call this package a hack, but the ratio of code/functionality is so
Xhigh that it doesn't seem right.
X
XTo install, edit the Makefile to get the "install" stuff right for your
Xsystem.  Then edit lwho.h if you want to change the spool directory.  When
Xinstalling a daemon on a host make sure that SPOOLDIR is appropriately
XNFS-mounted (and symlink'd) on the host.
X
XEnjoy!
X	/rich $alz; <rsalz@bbn.com>
END_OF_FILE
  if test 1376 -ne `wc -c <'README'`; then
    echo shar: \"'README'\" unpacked with wrong size!
  fi
  # end of 'README'
fi
if test -f 'MANIFEST' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'MANIFEST'\"
else
  echo shar: Extracting \"'MANIFEST'\" \(632 characters\)
  sed "s/^X//" >'MANIFEST' <<'END_OF_FILE'
X   File Name		Archive #	Description
X----------------------------------------------------------
XREADME                     1	Introduction
XMANIFEST                   1	This shipping list
XMakefile                   1	Guess...
Xluptime.1                  1	Manpage for luptime (see lwho.1)
Xlwho.1                     1	Manpage for lwho and luptime
Xlwho.c                     1	User program to view lwho data
Xlwho.h                     1	Header file for this package
Xlwhod.8                    1	Manpage for lwhod
Xlwhod.c                    1	Daemon to write lwhod data file
Xlwholib.c                  1	Library routines for this package
END_OF_FILE
  if test 632 -ne `wc -c <'MANIFEST'`; then
    echo shar: \"'MANIFEST'\" unpacked with wrong size!
  fi
  # end of 'MANIFEST'
fi
if test -f 'Makefile' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'Makefile'\"
else
  echo shar: Extracting \"'Makefile'\" \(1316 characters\)
  sed "s/^X//" >'Makefile' <<'END_OF_FILE'
X##
X##  Low-cost Remote Who
X##  There is no copyright on this package.
X##  $Header: /nfs/papaya/source/lwho/RCS/Makefile,v 1.2 91/03/16 18:29:38 rsalz Exp $
X##
X
X##  Installation destinations.
XK_GROUP	= wheel
XBIN	= /usr/local/bin
XETC	= /usr/etc
XMAN1	= /usr/man/man1
XMAN8	= /usr/man/man8
X
X##  Compilation flags.
XDEFS	=
XOPT	= -O
XCFLAGS	= $(OPT) $(DEFS)
X
X##
Xall:		lwho lwhod lwho.1 luptime.1 lwhod.8
X
Xinstall:	all
X	@echo Install according to local convention
X#	install lwho.1 $(MAN1)
X#	install luptime.1 $(MAN1)
X#	install lwhod.8 $(MAN8)
X#	@echo Make sure to update rc.local
X#	install -g $(K_GROUP) -m 2755 -s lwhod $(ETC)
X#	install -s lwho $(BIN)
X#	@rm -f $(BIN)/luptime
X#	ln $(BIN)/lwho $(BIN)/luptime
X
Xclean:
X	rm -f *.o lint* foo SHARFILE lwho lwhod
X
Xlint:		lint.u lint.d lint.m
X
Xshar:
X	makekit -m
X	@rm -f MANIFEST.BAK
X	@mv Part01 SHARFILE
X
X##
Xlint.u:		lwho
X	lint -a -b -h lwho.c lwholib.c >lint.u
Xlint.d:		lwhod
X	lint -a -b -h lwhod.c lwholib.c >lint.d
Xlint.m:		lwho.1 luptime.1 lwhod.8
X	checknr -c.BI.IR lwho.1 luptime.1 lwhod.8 >lint.m
X
X
X##
Xlwho:		lwho.o lwholib.o
X	@rm -f $@
X	$(CC) -o $@ $(OPT) lwho.o lwholib.o
X
Xlwhod:		lwhod.o lwholib.o
X	@rm -f $@
X	$(CC) -o $@ $(OPT) lwhod.o lwholib.o
X
X
X##
Xlwho.o lwhod.o lwholib.o:	lwho.h
X
X##
X.SUFFIXES:	.c. .o .src .obj
X.c.src:
X	#load $(CFLAGS) $<
X.c.obj:
X	#load $(CFLAGS) $<
END_OF_FILE
  if test 1316 -ne `wc -c <'Makefile'`; then
    echo shar: \"'Makefile'\" unpacked with wrong size!
  fi
  # end of 'Makefile'
fi
if test -f 'luptime.1' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'luptime.1'\"
else
  echo shar: Extracting \"'luptime.1'\" \(103 characters\)
  sed "s/^X//" >'luptime.1' <<'END_OF_FILE'
X.so man1/lwho.1
X.\" $Header: /nfs/papaya/source/lwho/RCS/luptime.1,v 1.2 91/03/16 18:31:56 rsalz Exp $
END_OF_FILE
  if test 103 -ne `wc -c <'luptime.1'`; then
    echo shar: \"'luptime.1'\" unpacked with wrong size!
  fi
  # end of 'luptime.1'
fi
if test -f 'lwho.1' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'lwho.1'\"
else
  echo shar: Extracting \"'lwho.1'\" \(2944 characters\)
  sed "s/^X//" >'lwho.1' <<'END_OF_FILE'
X.TH LWHO 1
X.\" $Header: /nfs/papaya/source/lwho/RCS/lwho.1,v 1.2 91/03/16 18:32:05 rsalz Exp $
X.SH NAME
Xlwho, luptime \- show who is using local machines
X.SH SYNOPSIS
X.B lwho
X[
X.BI \-u user
X] [
X.BI \-m machine
X] [
X.B \-x
X] [
X.B \-a
X] [
X.B \-i
X] [
X.B \-r
X]
X.PP
X.B luptime
X[
X.BI \-u user
X] [
X.BI \-m machine
X] [
X.B \-x
X] [
X.B \-a
X] [
X.B \-b
X] [
X.B \-l
X] [
X.B \-n
X] [
X.B \-u
X] [
X.B \-r
X]
X.PP
X.B a.out
X.B \-p
X[
X.I lwho_options
X]
X.PP
X.B a.out
X.B \-h
X[
X.I luptime_options
X]
X.SH DESCRIPTION
X.I Lwho
Xand
X.I luptime
Xare the user program for the ``low-cost rwho'' package.
XThe read the text files that
X.IR lwhod (8)
Xcreates in an NFS\-mounted spool directory, and presents the information
Xin a variety of useful ways.
XThe program is linked under two different names, and the output will
Xvary depending on how it is invoked.
XTo find host-oriented information, invoke
X.I luptime
Xor use the ``\-h'' flag.
XTo find person-oriented information, invoke
X.I lwho
Xor use the ``\-p'' flag.
X.PP
XBy default, the programs
Xshow information about all users on all hosts.
XTo see information about a specified set of users or hosts, use the ``\-u''
Xflag specify the login names of the desired users, or
Xthe ``\-m'' flag to specify the names of the desired hosts.
XThese flags may be repeated to specify a collection of hosts or users,
Xand may be used together to specify an intersection.
XFor example:
X.RS
X.nf
Xlwho \-u rsalz
Xlwho \-u root \-m fig.bbn.com \-m pineapple.bbn.com
Xlwho \-m fig.bbn.com \-m pineapple.bbn.com
X.fi
X.RE
XThe first line finds all machines where ``rsalz'' is logged in; the
Xsecond line finds if ``root'' is logged in on ``fig'' or ``pineapple'';
Xthe third line shows all users logged in to either of those machines.
X.PP
XWhen reporting, output is normally sorted alphabetically by the host or
Xuser name; this may be specified by using tyhe ``\-a'' flag.
XTo sort by uptime (first machine booted first), use the ``\-b'' flag.
XTo sort by the number of users (smallest number first), use the ``\-n'' flag.
XTo sort by the load average (most lightly-loaded machine first), use the
X\&``\-l'' flag.
XTo sort by idle time (most recently used terminal first), use the ``\-i'' flag.
XTo reverse the sorting, regardless of the method, use the ``\-r'' flag.
XFor
X.IR lwho ,
Xonly the ``\-i'' flag makes sense, if any other flag is given it is ignored.
XFor
X.IR luptime ,
Xthe ``\-i'' flag is ignored.
X.PP
XIf no users (for
X.IR lwho ),
Xor no hosts (for
X.IR luptime ),
Xare found, no output is generated.
XTo suppress the banner (useful when feeding into a pipeline),
Xuse the ``\-x'' flag.
X.SH BUGS
XThere are no hueristics to tell if a host is up or down, or ignore users
Xwho have been idle for a long time the way
X.IR rwho (1)
Xand
X.IR ruptime (1)
Xcan and do.
X.SH FILES
X.ta \w'/usr/spool/lwho/\fIhostname\fP  'u
X/usr/spool/lwho/\fIhostname\fP	Load and user data
X.SH AUTHOR
XRich $alz; <rsalz@bbn.com>.uu.net>.
X.sp
XThere is no copyright on this package.
X.SH "SEE ALSO"
Xlwhod(8).
END_OF_FILE
  if test 2944 -ne `wc -c <'lwho.1'`; then
    echo shar: \"'lwho.1'\" unpacked with wrong size!
  fi
  # end of 'lwho.1'
fi
if test -f 'lwho.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'lwho.c'\"
else
  echo shar: Extracting \"'lwho.c'\" \(10385 characters\)
  sed "s/^X//" >'lwho.c' <<'END_OF_FILE'
X/*
X**  Low-cost Remote Who
X**  There is no copyright on this package.
X**  User program to read the data files.
X*/
X#include "lwho.h"
X#include <sys/types.h>
X#include <dirent.h>
X#if	!defined(lint) && !defined(SABER)
Xstatic char RCS[] =
X	"$Header: /nfs/papaya/source/lwho/RCS/lwho.c,v 1.4 91/03/16 18:39:35 rsalz Exp $";
X#endif	/* !defined(lint) && !defined(SABER) */
X
Xtypedef struct _HOSTDATA {
X    int			Valid;
X    int			Users;
X    time_t		BootTime;
X    double		LoadAve[3];
X    char		Name[HOSTLEN];
X} HOSTDATA;
X
Xtypedef struct _USERDATA {
X    int			Valid;
X    time_t		Onsince;
X    time_t		Idle;
X    char		Name[NAMELEN];
X    char		Line[LINELEN];
X    HOSTDATA		*Host;
X} USERDATA;
X
X
X#define HOST_DELTA	20
XSTATIC HOSTDATA		*Hosts;
X
X#define USER_DELTA	20
XSTATIC USERDATA		*Users;
X
X
X/*
X**  Get a free host entry, growing as needed.
X*/
XSTATIC HOSTDATA *
XGetHost()
X{
X    static HOSTDATA	*Last;
X    static int		Count;
X    static int		Size;
X
X    /* Give last one back if they didn't fill it in. */
X    if (Last && !Last->Valid)
X	return Last;
X
X    if (Hosts == NULL) {
X	/* First time here, get space. */
X	Count = 0;
X	Size = HOST_DELTA;
X	Hosts = NEW(HOSTDATA, Size);
X    }
X    else if (Count >= Size - 1) {
X	/* Need more space. */
X	Size += HOST_DELTA;
X	Hosts = RENEW(Hosts, HOSTDATA, Size);
X    }
X
X    Hosts[Count].Valid = FALSE;
X    Last = &Hosts[Count++];
X    return Last;
X}
X
X
X/*
X**  Get a free user entry, growing as needed.
X*/
XSTATIC USERDATA *
XGetUser()
X{
X    static USERDATA	*Last;
X    static int		Count;
X    static int		Size;
X
X    /* Give last one back if they didn't fill it in. */
X    if (Last && !Last->Valid)
X	return Last;
X
X    if (Users == NULL) {
X	/* First time here, get space. */
X	Count = 0;
X	Size = USER_DELTA;
X	Users = NEW(USERDATA, Size);
X    }
X    else if (Count >= Size - 1) {
X	/* Need more space. */
X	Size += USER_DELTA;
X	Users = RENEW(Users, USERDATA, Size);
X    }
X
X    Users[Count].Valid = FALSE;
X    Last = &Users[Count++];
X    return Last;
X}
X
X
X/*
X**  Sort host data by host name.
X*/
XSTATIC int
XHSalpha(p1, p2)
X    char	*p1;
X    char	*p2;
X{
X    HOSTDATA	*h1;
X    HOSTDATA	*h2;
X
X    h1 = (HOSTDATA *)p1;
X    h2 = (HOSTDATA *)p2;
X    return strcmp(h1->Name, h2->Name);
X}
X
X
X/*
X**  Sort host data by time booted.
X*/
XSTATIC int
XHSboottime(p1, p2)
X    char	*p1;
X    char	*p2;
X{
X    HOSTDATA	*h1;
X    HOSTDATA	*h2;
X    long	l;
X
X    h1 = (HOSTDATA *)p1;
X    h2 = (HOSTDATA *)p2;
X    if (l = h1->BootTime - h2->BootTime)
X	return l < 0 ? -1 : 1;
X    return 0;
X}
X
X
X/*
X**  Sort host data by host load average.
X*/
XSTATIC int
XHSloadave(p1, p2)
X    char	*p1;
X    char	*p2;
X{
X    HOSTDATA	*h1;
X    HOSTDATA	*h2;
X    long	 l;
X
X    h1 = (HOSTDATA *)p1;
X    h2 = (HOSTDATA *)p2;
X    if (l = h1->LoadAve[0] - h2->LoadAve[0])
X	return l < 0 ? -1 : 1;
X    return 0;
X}
X
X
X/*
X**  Sort host data by number of users.
X*/
XSTATIC int
XHSnumusers(p1, p2)
X    char	*p1;
X    char	*p2;
X{
X    HOSTDATA	*h1;
X    HOSTDATA	*h2;
X
X    h1 = (HOSTDATA *)p1;
X    h2 = (HOSTDATA *)p2;
X    return h1->Users - h2->Users;
X}
X
X
X/*
X**  Sort user data alphabetically, than by host, than by idle time.
X*/
XSTATIC int
XUSalpha(p1, p2)
X    char	*p1;
X    char	*p2;
X{
X    USERDATA	*u1;
X    USERDATA	*u2;
X    int		 i;
X    long	 l;
X    
X    u1 = (USERDATA *)p1;
X    u2 = (USERDATA *)p2;
X    if (i = strcmp(u1->Name, u2->Name))
X	return i;
X    if (i = strcmp(u1->Host->Name, u2->Host->Name))
X	return i;
X    if (l = u1->Idle - u2->Idle)
X	return l < 0 ? -1 : 1;
X    return 0;
X}
X
X
X/*
X**  Sort user data by idle time.
X*/
XSTATIC int
XUSidle(p1, p2)
X    char	*p1;
X    char	*p2;
X{
X    USERDATA	*u1;
X    USERDATA	*u2;
X    long	 l;
X
X    u1 = (USERDATA *)p1;
X    u2 = (USERDATA *)p2;
X    if (l = u1->Idle - u2->Idle)
X	return l < 0 ? -1 : 1;
X    return 0;
X}
X
X
X/*
X**  Return a formatted string with how long between a given time and now.
X*/
XSTATIC char *
XHowLong(t)
X    time_t		t;
X{
X    static char		buff[10];
X    int			hours;
X
X    /* If out of range, return gunk. */
X    if (t < 0 || t > 99 * 24 * 60 * 60)
X	return "??:??";
X
X    /* Get minutes and hours from seconds. */
X    t = (t + 59) / 60;
X    hours = t / 60;
X
X    if (hours >= 24)
X	(void)sprintf(buff, "%02dd  ", hours / 24);
X    else
X	(void)sprintf(buff, "%02d:%02d", hours, t - hours * 60);
X    return buff;
X}
X
X
X/*
X**  Split the cruft out of a ctime() result.
X*/
XSTATIC char *
XMyctime(t)
X    time_t	*t;
X{
X    char	*p;
X
X    p = ctime(t);
X    p[19] = '\0';
X    return p + 4;
X}
X
X
X/*
X**  Do luptime output.
X*/
XSTATIC void
XShowHostData(Sorter, Reversed, Banner)
X    int			Sorter;
X    int			Reversed;
X    int			Banner;
X{
X    register HOSTDATA	*H;
X    register int	i;
X    time_t		now;
X
X    /* Count those pups. */
X    for (H = Hosts, i = 0; H->Valid; H++, i++)
X	;
X    if (i == 0)
X	return;
X
X    /* Sort that sucker. */
X    switch (Sorter) {
X    default:
X    case 'a':
X    case 'i':
X	qsort((char *)Hosts, i, sizeof *Hosts, HSalpha);
X	break;
X    case 'b':
X	qsort((char *)Hosts, i, sizeof *Hosts, HSboottime);
X	break;
X    case 'l':
X	qsort((char *)Hosts, i, sizeof *Hosts, HSloadave);
X	break;
X    case 'n':
X	qsort((char *)Hosts, i, sizeof *Hosts, HSnumusers);
X	break;
X    }
X
X    if (Banner)
X	printf("%*s Users  Load Average   Up    Booted\n",
X	    -HOSTLEN / 2, "Host");
X
X    /* Watch the clock. */
X    now = time((time_t *)NULL);
X
X    /* Dump the data. */
X    if (Reversed) {
X	Reversed = -1;
X	H = &Hosts[i - 1];
X    }
X    else {
X	Reversed = 1;
X	H = &Hosts[0];
X    }
X    for ( ; --i >= 0; H += Reversed)
X	printf("%*s %-5d  %4.2f %4.2f %4.2f %s %s\n",
X	    -HOSTLEN / 2, H->Name,
X	    H->Users,
X	    H->LoadAve[0], H->LoadAve[1], H->LoadAve[2],
X	    HowLong(now - H->BootTime), Myctime(&H->BootTime));
X}
X
X
X/*
X**  Do lwho output.
X*/
XSTATIC void
XShowUserData(Sorter, Reversed, Banner)
X    int			Sorter;
X    int			Reversed;
X    int			Banner;
X{
X    register USERDATA	*U;
X    register int	i;
X
X    /* Count those pups. */
X    for (U = Users, i = 0; U->Valid; U++, i++)
X	;
X    if (i == 0)
X	return;
X
X    /* Sort that sucker. */
X    switch (Sorter) {
X    default:
X    case 'a':
X    case 'b':
X    case 'l':
X    case 'n':
X	qsort((char *)Users, i, sizeof *Users, USalpha);
X	break;
X    case 'i':
X	qsort((char *)Users, i, sizeof *Users, USidle);
X	break;
X    }
X
X    if (Banner)
X	printf("%*s %*s %*s %s %s\n",
X	    -NAMELEN, "Name",
X	    -HOSTLEN / 2, "Host",
X	    -LINELEN, "TTY",
X	    "Idle", " On since");
X
X    /* Dump the data. */
X    if (Reversed) {
X	Reversed = -1;
X	U = &Users[i - 1];
X    }
X    else {
X	Reversed = 1;
X	U = &Users[0];
X    }
X    for ( ; --i >= 0; U += Reversed)
X	printf("%*s %*s %*s %s %s\n",
X	    -NAMELEN, U->Name,
X	    -HOSTLEN / 2, U->Host->Name,
X	    -LINELEN, U->Line,
X	    HowLong(U->Idle), Myctime(&U->Onsince));
X}
X
X
X/*
X**  Return TRUE if a string is a member of a NULL-terminated array.
X*/
XSTATIC int
XFound(argv, p)
X    register char	**argv;
X    register char	*p;
X{
X    for ( ; *argv; argv++)
X	if (**argv == *p && strcmp(*argv, p) == 0)
X	    return TRUE;
X    return FALSE;
X}
X
X
Xmain(ac, av)
X    int			ac;
X    char		*av[];
X{
X    register HOSTDATA	*H;
X    register USERDATA	*U;
X    register DIR	*dp;
X    register FILE	*F;
X    register int	i;
X    struct dirent	*ep;
X    char		buff[BUFFLEN];
X    char		**Ihosts;
X    char		**Iusers;
X    int			Ihostcount;
X    int			Iusercount;
X    int			DoHosts;
X    int			Reversed;
X    int			SortMode;
X    int			Banner;
X    long		l[3];
X
X    /* Get program name. */
X    if (progname = strrchr(av[0], '/'))
X	progname++;
X    else
X	progname = av[0];
X    DoHosts = strcmp(progname, "luptime") == 0;
X
X    /* Set up defaults and arrays of "interesting" hosts/users. */
X    Reversed = FALSE;
X    Banner = TRUE;
X    SortMode = 'a';
X    Ihosts = NEW(char*, ac + 1);
X    Iusers = NEW(char*, ac + 1);
X    Ihostcount = 0;
X    Iusercount = 0;
X
X    /* Parse JCL. */
X    while ((i = getopt(ac, av, "abilnrhpxm:u:")) != EOF)
X	switch (i) {
X	default:
X	    exit(1);
X	case 'a':
X	case 'b':
X	case 'i':
X	case 'l':
X	case 'n':
X	    SortMode = i;
X	    break;
X	case 'r':
X	    Reversed = TRUE;
X	    break;
X	case 'h':
X	    DoHosts = TRUE;
X	    break;
X	case 'p':
X	    DoHosts = FALSE;
X	    break;
X	case 'x':
X	    Banner = FALSE;
X	case 'm':
X	    Ihosts[Ihostcount++] = optarg;
X	    break;
X	case 'u':
X	    Iusers[Iusercount++] = optarg;
X	    break;
X	}
X    Iusers[Iusercount] = NULL;
X    Ihosts[Ihostcount] = NULL;
X
X    /* Go to spool directory, open it. */
X    if (chdir(SPOOLDIR) < 0) {
X	fprintf(stderr, "Can't cd to \"%s\", %s.\n", SPOOLDIR,
X	    strerror(errno));
X	exit(1);
X    }
X    if ((dp = opendir(".")) == NULL) {
X	fprintf(stderr, "Can't open directory, %s.\n", strerror(errno));
X	exit(1);
X    }
X
X    /* Read data. */
X    while (ep = readdir(dp)) {
X	/* Is this a file we're interested and that we can access? */
X	if (strcmp(ep->d_name, ".") == 0
X	 || strcmp(ep->d_name, "..") == 0
X	 || (F = fopen(ep->d_name, "r")) == NULL)
X	    continue;
X
X	/* Check version. */
X	if (fgets(buff, sizeof buff, F) == NULL
X	 || strncmp(buff, VERSION, sizeof VERSION - 1) != 0) {
X	    (void)fclose(F);
X	    continue;
X	}
X
X	/* Is this a host the user is interested in? */
X	if (Ihostcount && !Found(Ihosts, ep->d_name)) {
X	    (void)fclose(F);
X	    continue;
X	}
X
X	/* Get space, store the name. */
X	H = GetHost();
X	(void)strncpy(H->Name, ep->d_name, sizeof H->Name);
X	H->Name[sizeof H->Name - 1] = '\0';
X
X	/* Get boot time. */
X	if (fgets(buff, sizeof buff, F) == NULL) {
X	    (void)fclose(F);
X	    continue;
X	}
X	H->BootTime = atol(buff);
X
X	/* Get load average. */
X	if (fgets(buff, sizeof buff, F) == NULL) {
X	    (void)fclose(F);
X	    continue;
X	}
X	if (sscanf(buff, "%ld %ld %ld", &l[0], &l[1], &l[2]) != 3) {
X	    (void)fclose(F);
X	    continue;
X	}
X	H->LoadAve[0] = l[0] / 100.;
X	H->LoadAve[1] = l[1] / 100.;
X	H->LoadAve[2] = l[2] / 100.;
X
X	/* Get users. */
X	for (H->Users = 0; fgets(buff, sizeof buff, F); ) {
X
X	    /* Logical end of file? */
X	    if (strncmp(buff, VERSION, sizeof VERSION - 1) == 0)
X		break;
X
X	    /* Get space, read in a line. */
X	    U = GetUser();
X	    if (sscanf(buff, "%s %s %ld %ld",
X		    U->Name, U->Line, &U->Onsince, &U->Idle) != 4)
X		continue;
X	    H->Users++;
X
X	    /* Is this a user the user is interested in? */
X	    if (Iusercount && !Found(Iusers, U->Name))
X		continue;
X
X	    U->Host = H;
X	    U->Valid = TRUE;
X	}
X
X	H->Valid = TRUE;
X
X	(void)fclose(F);
X
X    }
X    (void)closedir(dp);
X
X    /* Print data. */
X    if (DoHosts)
X	ShowHostData(SortMode, Reversed, Banner);
X    else
X	ShowUserData(SortMode, Reversed, Banner);
X
X    /* That's it. */
X    exit(0);
X    /* NOTREACHED */
X}
END_OF_FILE
  if test 10385 -ne `wc -c <'lwho.c'`; then
    echo shar: \"'lwho.c'\" unpacked with wrong size!
  fi
  # end of 'lwho.c'
fi
if test -f 'lwho.h' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'lwho.h'\"
else
  echo shar: Extracting \"'lwho.h'\" \(1614 characters\)
  sed "s/^X//" >'lwho.h' <<'END_OF_FILE'
X/*
X**  Low-cost Remote Who
X**  There is no copyright on this package.
X**  Header file for the package.
X**
X**  $Header: /nfs/papaya/source/lwho/RCS/lwho.h,v 1.4 91/03/16 18:36:23 rsalz Exp $
X*/
X#include <stdio.h>
X#include <sys/types.h>
X#include <sys/file.h>
X#include <time.h>
X
X
X/*
X**  Configuration parameters.
X*/
X/*#define SIG_CATCHER	int			/* Older systems	*/
X#define SIG_CATCHER	void			/* Modern systems	*/
X#define NAP_TIME	60			/* How often to update	*/
X#define SPOOLDIR	"/usr/spool/lwho"	/* Data directory	*/
X#define VERSION		"R$1"			/* Version and EOF mark	*/
X#undef  DEBUG					/* Having problems?	*/
X#undef  DAEMON_MUST_RUN_AS_ROOT			/* Do setuid(0)?	*/
X
X
X/*
X**  Debugging control.
X*/
X#if	defined(DEBUG) || defined(SABER)
X#define	STATIC		/* NULL */
X#else
X#define STATIC		static
X#endif	/* defined(DEBUG) || defined(SABER) */
X
X
X/*
X**  Various constants, you can change these if you reall want to.
X*/
X#define TRUE		1
X#define FALSE		0
X
X#define BUFFLEN		128
X#define NAMELEN		10
X#define LINELEN		10
X#define HOSTLEN		40
X
X
X/*
X**  Syntactic sugar.
X*/
X#define NEW(T, i)		\
X	(T *)xmalloc((unsigned int)i * sizeof (T))
X#define RENEW(p, T, c)		\
X	((T *)xrealloc((char *)(p), (unsigned int)(sizeof (T) * (c))))
X
X
X/*
X**  From our meager little library.
X*/
Xextern char	*progname;
Xextern int	*xmalloc();
Xextern int	*xrealloc();
Xextern char	*strerror();
X
X
X/*
X**  From the C library.
X*/
Xextern char	*optarg;
Xextern int	errno;
Xextern long	atol();
Xextern long	lseek();
Xextern time_t	time();
Xextern char	*sprintf();		/* Painful my ass!	*/
Xextern char	*strrchr();
Xextern char	*strcpy();
Xextern char	*strncpy();
Xextern void	exit();
END_OF_FILE
  if test 1614 -ne `wc -c <'lwho.h'`; then
    echo shar: \"'lwho.h'\" unpacked with wrong size!
  fi
  # end of 'lwho.h'
fi
if test -f 'lwhod.8' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'lwhod.8'\"
else
  echo shar: Extracting \"'lwhod.8'\" \(1606 characters\)
  sed "s/^X//" >'lwhod.8' <<'END_OF_FILE'
X.\" $Header: /nfs/papaya/source/lwho/RCS/lwhod.8,v 1.3 91/03/16 18:32:49 rsalz Exp $
X.TH LWHOD 8
X.SH NAME
Xlwhod \- low-cost rwho daemon
X.SH SYNOPSIS
X.B /usr/etc/lwhod
X.SH DESCRIPTION
X.I Lwhod
Xis the demon for the ``low-cost rwho'' package.
XIt is typically started out of
X.I /etc/rc.local
Xat system boot-time.
X.PP
XThe daemon periodically updates a file with the system's host name
Xin a spool directory.
XThis file is a self-describing text file; a sample entry looks like
Xthis:
X.RS
X.nf
XR$1
X606853420 # boottime
X56 30 2 # loadave sun-style
Xrsalz console 606853499 1024662 # tty user.line.onsince.idle
Xrsalz ttyp0 607872590 771 # tty user.line.onsince.idle
Xrsalz ttyp1 606853700 766 # tty user.line.onsince.idle
Xrsalz ttyp2 606853705 765 # tty user.line.onsince.idle
Xrsalz ttyp3 606853705 765 # tty user.line.onsince.idle
Xrsalz ttyp5 607797162 0 # tty user.line.onsince.idle
XR$1
X.fi
X.RE
XThe pound signs and the comments are part of the file, but are ignored
Xby the user program.
XThe system boot time and the user ``onsince'' time are given as the
Xnumber of seconds since the standard Unix epoch.
XThe load average is the standard system load average, multiplied by 100.
XThe ``R$1'' strings that are used to indicate the file version, and
Xmark the end of the file.
X(Since the daemon keeps the data file open, if people log off the file will
Xnot shrink and a logical EOF marker is needed.)
X.SH FILES
X.ta \w'/usr/spool/lwho/\fIhostname\fP  'u
X/usr/spool/lwho/\fIhostname\fP	Load and user data for this host.
X.SH AUTHOR
XRich $alz; <rsalz@bbn.com>.
X.sp
XThere is no copyright on this package.
X.SH "SEE ALSO"
Xlwho(1).
END_OF_FILE
  if test 1606 -ne `wc -c <'lwhod.8'`; then
    echo shar: \"'lwhod.8'\" unpacked with wrong size!
  fi
  # end of 'lwhod.8'
fi
if test -f 'lwhod.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'lwhod.c'\"
else
  echo shar: Extracting \"'lwhod.c'\" \(4905 characters\)
  sed "s/^X//" >'lwhod.c' <<'END_OF_FILE'
X/*
X**  Low-cost Remote Who
X**  There is no copyright on this package.
X**  Deamon to write the data file.
X*/
X#include "lwho.h"
X#include <signal.h>
X#include <nlist.h>
X#include <utmp.h>
X#include <sgtty.h>
X#include <sys/stat.h>
X#ifdef	sun
X#include <sys/param.h>
X#endif	/* sun */
X#if	!defined(lint) && !defined(SABER)
Xstatic char RCS[] =
X	"$Header: /nfs/papaya/source/lwho/RCS/lwhod.c,v 1.3 91/03/16 18:29:11 rsalz Exp $";
X#endif	/* !defined(lint) && !defined(SABER) */
X
XSTATIC FILE	*F;
XSTATIC char	 Host[BUFFLEN];
X
XSTATIC struct nlist	Nlist[] = {
X    { "_avenrun" },
X#define NL_AVENRUN	0
X    { "_boottime" },
X#define NL_BOOTTIME	1
X    NULL
X};
X
X
X/*
X**  We're going down:  close open file, remove our data file.
X*/
XSTATIC SIG_CATCHER
XCatcher()
X{
X    (void)fclose(F);
X    (void)unlink(Host);
X    exit(1);
X}
X
X
Xmain()
X{
X    register FILE	*U;
X    register int	i;
X    register int	Kmem;
X    struct utmp		Uentry;
X    struct stat		Sb;
X    char		buff[BUFFLEN];
X    char		line[20];
X    char		name[20];
X    time_t		Now;
X    int			BootTime;
X#ifdef	vax
X    double		avenrun[3];
X#else
X#ifdef	sun
X    int			avenrun[3];
X#endif	/* sun */
X    /* put other types here... */
X#endif	/* vax */
X
X    progname = "rwhod";
X
X#ifdef	DAEMON_MUST_RUN_AS_ROOT
X    if (getuid()) {
X	fprintf(stderr, "%s: permission denied, %s.\n",
X	    progname, strerror(errno));
X	exit(1);
X    }
X#endif	/* DAEMON_MUST_RUN_AS_ROOT */
X
X    /* Close stdin, stdout. */
X    (void)close(0);
X    (void)close(1);
X
X#if 	!defined(DEBUG) && !defined(SABER)
X    if (fork() > 0)
X	exit(0);
X#ifdef	TIOCNOTTY
X    if ((i = open("/dev/tty", O_RDWR)) >= 0) {
X	(void)ioctl(i, TIOCNOTTY, (char *)0);
X	(void)close(i);
X    }
X#endif	/* TIOCNOTTY */
X#endif	/* !defined(DEBUG) && !defined(SABER) */
X
X    if (chdir(SPOOLDIR) < 0) {
X	fprintf(stderr, "%s: can't cd to \"%s\", %s.\n",
X	    progname, SPOOLDIR, strerror(errno));
X	exit(1);
X    }
X
X    if (gethostname(Host, sizeof Host) < 0) {
X	fprintf(stderr, "%s: can't get hostname, %s.\n",
X	    progname, strerror(errno));
X	exit(1);
X    }
X
X    /* Read namelist from kernel. */
X    Nlist[NL_AVENRUN].n_value = 0;
X    Nlist[NL_BOOTTIME].n_value = 0;
X    if (nlist("/vmunix", Nlist) < 0
X     || Nlist[NL_AVENRUN].n_value == 0
X     || Nlist[NL_BOOTTIME].n_value == 0) {
X	fprintf(stderr, "%s: can't read namelist, %s.\n",
X		progname, strerror(errno));
X	exit(1);
X    }
X
X    /* Open kmem, get boot time. */
X    if ((Kmem = open("/dev/kmem", O_RDONLY)) < 0) {
X	fprintf(stderr, "%s: can't read kmem, %s.\n",
X	    progname, strerror(errno));
X	exit(1);
X    }
X    (void)lseek(Kmem, (long)Nlist[NL_BOOTTIME].n_value, L_SET);
X    (void)read(Kmem, (char *)&BootTime, sizeof BootTime);
X
X    /* Open utmp. */
X    if ((U = fopen("/etc/utmp", "r")) == NULL) {
X	fprintf(stderr, "%s: can't read utemp, %s.\n",
X	    progname, strerror(errno));
X	exit(1);
X    }
X
X    /* Open our output file, set big buffering. */
X    if ((i = open(Host, O_RDWR | O_CREAT | O_TRUNC, 0666)) < 0) {
X	fprintf(stderr, "%s: can't create \"%s\", %s.\n",
X	    progname, strerror(errno));
X	exit(1);
X    }
X    F = fdopen(i, "w+");
X    setbuf(F, NEW(char, BUFSIZ));
X
X    /* Arrange to remove file on a termination signal. */
X    (void)signal(SIGHUP, Catcher);
X    (void)signal(SIGINT, Catcher);
X    (void)signal(SIGQUIT, Catcher);
X    (void)signal(SIGALRM, Catcher);
X    (void)signal(SIGTERM, Catcher);
X
X    /* Enter the land of for-ever. */
X    for ( ; ; ) {
X	rewind(F);
X	fprintf(F, "%s\n", VERSION);
X	fprintf(F, "%ld # boottime\n", (long)BootTime);
X
X	/* Do the load average. */
X	(void)lseek(Kmem, (long)Nlist[NL_AVENRUN].n_value, L_SET);
X	(void)read(Kmem, (char *)avenrun, sizeof avenrun);
X#ifdef	vax
X	for (i = 0; i < 3; i++)
X	    fprintf(F, "%ld ", (long)(avenrun[i] * 100));
X	fprintf(F, "# loadave vax-style\n");
X#else
X#ifdef	sun
X	for (i = 0; i < 3; i++)
X	    fprintf(F, "%ld ", (long)(avenrun[i] / (FSCALE / 100)));
X	fprintf(F, "# loadave sun-style\n");
X#else
X	/* put other types here... */
X	fprintf(F, "0 0 0 # loadave unknown\n");
X#endif	/* sun */
X#endif	/* vax */
X
X	/* Write utmp entries. */
X	rewind(U);
X	(void)time(&Now);
X	while (fread((char *)&Uentry, sizeof Uentry, 1, U) == 1) {
X	    if (Uentry.ut_time == 0 || Uentry.ut_name[0] == '\0')
X		continue;
X
X#if	0
X#ifdef	nonuser
X	    /*  List multiple entries for window-systems users? */
X	    if (nonuser(Uentry))
X		continue;
X#endif	/* nonuser */
X#endif	/* 0 */
X
X	    /* Get user name and the line they're on. */
X	    (void)strncpy(name, Uentry.ut_name, sizeof name);
X	    name[sizeof Uentry.ut_name - 1] = '\0';
X	    (void)strncpy(line, Uentry.ut_line, sizeof line);
X	    line[sizeof Uentry.ut_line - 1] = '\0';
X
X	    (void)sprintf(buff, "/dev/%s", line);
X
X	    fprintf(F, "%s %s %ld %ld # tty user.line.onsince.idle\n",
X		name, line, (long)Uentry.ut_time,
X		stat(buff, &Sb) < 0 ? 0L : (long)Now - Sb.st_atime);
X	}
X
X	fprintf(F, "%s\n", VERSION);
X	/* Force updates, to take a nap. */
X	(void)fflush(F);
X	(void)sleep(NAP_TIME);
X    }
X}
END_OF_FILE
  if test 4905 -ne `wc -c <'lwhod.c'`; then
    echo shar: \"'lwhod.c'\" unpacked with wrong size!
  fi
  # end of 'lwhod.c'
fi
if test -f 'lwholib.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'lwholib.c'\"
else
  echo shar: Extracting \"'lwholib.c'\" \(1188 characters\)
  sed "s/^X//" >'lwholib.c' <<'END_OF_FILE'
X/*
X**  Low-cost Remote Who
X**  There is no copyright on this package.
X**  Library routines.
X*/
X/* LINTLIBRARY */
X#include "lwho.h"
X#if	!defined(lint) && !defined(SABER)
Xstatic char RCS[] =
X	"$Header: /nfs/papaya/source/lwho/RCS/lwholib.c,v 1.2 91/03/16 18:29:13 rsalz Exp $";
X#endif	/* !defined(lint) && !defined(SABER) */
X
X
Xchar	*progname = "a.out";
X
X
X/*
X**  Print error message text.
X*/
Xchar *
Xstrerror(e)
X{
X    extern int		 sys_nerr;
X    extern char		*sys_errlist[];
X    static char		 buff[30];
X
X    if (e > 0 && e < sys_nerr)
X	return sys_errlist[e];
X    (void)sprintf(buff, "Error code %d", e);
X    return buff;
X}
X
X
Xint *
Xxmalloc(i)
X    unsigned int	 i;
X{
X    extern char		*malloc();
X    char		*p;
X
X    if ((p = malloc(i)) == NULL) {
X	/* Print as normal int so bogus values show up easily. */
X	fprintf(stderr, "%s: Can't malloc %d bytes\n", progname, i);
X	exit(1);
X    }
X    return (int *)p;
X}
X
X
Xint *
Xxrealloc(p, i)
X    char		*p;
X    unsigned int	 i;
X{
X    extern char		*realloc();
X
X    if ((p = realloc(p, i)) == NULL) {
X	/* Print as normal int so bogus values show up easily. */
X	fprintf(stderr, "%s: Can't realloc %d bytes\n", progname, i);
X	exit(1);
X    }
X    return (int *)p;
X}
END_OF_FILE
  if test 1188 -ne `wc -c <'lwholib.c'`; then
    echo shar: \"'lwholib.c'\" unpacked with wrong size!
  fi
  # end of 'lwholib.c'
fi
echo shar: End of archive 1 \(of 1\).
cp /dev/null ark1isdone
MISSING=""
for I in 1 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have the archive.
    rm -f ark[1-9]isdone
else
    echo You still must unpack the following archives:
    echo "        " ${MISSING}
fi
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.