[net.sources] lu.c -- list user program.

mikec@reed.UUCP (Michael Cooper) (11/23/84)

[ All lines are not created equal .... ]

This is a reposting of lu.c.  I have had many bug reports and changes.
Instead of sending everyone new copies, I am reposting it since there where
so many responses.  See README for more info on the changes/fixes.

					Michael Cooper
					tektronix!reed!mikec

#----------C--U--T-----H--E--R--E---------------C--U--T-------H--E--R--E-------
# This is a shell archive.  Remove anything before this line, then
# unpack it by saving it in a file and typing sh file.  (Files
# unpacked will be owned by you and have default permissions.)
#
# Date Created: Thu Nov 22 13:43:43 PST 1984
# Contents of archive:
# Makefile README lav.c lu.1 lu.c
echo x - lu.1
sed 's/^x//' >lu.1 <<'!Funky!Stuff!'
x.TH LU 1 
x.SH NAME
xlu \- list users
x.SH SYNOPSIS
x.B lu 
x[ 
x.B \-s
x]
x[
x.B \-f 
xfilename
x] 
x[
x.B user1
x.B user2 ... 
x.B usern
x]
x.br
x.SH DESCRIPTION
x.I lu
xlists the users who are logged on continuously using curses(3).
xIts display is similiar to that of who(1).
xIt prints the name of the user, the tty which they are using,
xthe time that user logged on, their group name, and the idle time.
xThe bottom status line contains, the current time, the load average, 
xthe number of users and whether or not their is mail.
xTo redraw the screen, type quit (^\\).  To exit, type interupt (^C or ^?).
x.B lu
xrequires that you use a CRT with a known termcap entry, since it 
xuses curses(3) to display the information in the above manner.
x.PP
xIf you supply user names on the command line, it will print out that
xpersons uid, group name, group id, home directory, and login shell.
x.PP
xThe following options are currently supported:
x.TP 10
x.B "s"
xDon't use standout mode.
x.TP 10
x.B "f filename"
xUse 'filename' as an alternitive file to /etc/utmp.
x.br
x.sp 1
x.SH "SEE ALSO"
xwho(1) curses(3)
x.sp 1
x.SH DIAGNOSTICS
xThere are no diagnostics.
x.sp 1
x.SH AUTHORS
xOrignally written by Matt Giger.
x.br
xTotally re-written by Michael Cooper (tektronix!reed!mikec)
x.sp 1
x.SH BUGS
x.PP
xStart-up is VERY slow.
x.PP
xDue to the slowness of some of the routines, lu never 'sleeps'.  It is
xconstantly doing something.  This includes checking for mail, looking up
xgroup names, idle times, etc.  The more loaded the system is, the less
xoften the screen is updated.
!Funky!Stuff!
echo x - Makefile
cat >Makefile <<'!Funky!Stuff!'
#
# Makefile for lu
#
# 11-1-84	reed!mikec
#

CC = cc
LIBS = -lcurses -ltermcap
# destination directory
BIN = /bin
# location of lav program if not using pdp11
LAV = /usr/local/lav
# local defines.
DEFS= -DREED -DLOADAV=\"${LAV}\"

lu: lu.c
	${CC} -s $(DEFS) lu.c -o lu ${LIBS}

lav: lav.c
	${CC} -s lav.c -o lav

install: lu lav
	mv lu ${BIN}
	mv lav ${LAV}
	chmod 2755 ${LAV}

man: lu.1
	nroff -man lu.1 > lu.man

all: lu lav man
!Funky!Stuff!
echo x - README
cat >README <<'!Funky!Stuff!'
11-22-84						Michael Cooper

lu.c was originally written by Matt Giger.  I have extensively hacked it
into something that resembles nothing of the original program.  In doing
so, I have also fixed almost all of the numorous bugs.  For more info
on what lu does, see the manual.

This is version 1.2 of lu.   Keith Bostic (seismo!keith) has worked 
on lu and has come up with the following changes.  Some of the changes
were not incorporated due to the fact that they where not good for the rest
of the world. 

======================================================================

*** BUG ***
1:	If login name was 8 chars, would drop core 'cause the utmp field
		was filled up.  Did it correctly in one part of the code,
		incorrectly in another.
2:	If there were more than LINES users, it dropped 2 of them.  Fencepost
		error in mvprintw statement.

*** FIX ***
1:	If you specify a non-standard utmp, program should give error (as
		minimum!) if it can't read the file.  Changed it so it
		dies with error message; required change to manual page.
		Also, it was closing/reopening the file on every iteration,
		changed so it rewound file instead.
2:	Ran through lint, password, group, time, ctime, lseek, etc. etc.
		not declared correctly.  Removed bunches of unnecessary
		variables.  Removed alarm stuff, never used.
3:	Doesn't bomb out after 1 wrong user, attempts the rest of them too.
		Also, understands if can't get a group -- just enters
		unknown in the field -- a lot of systems don't keep
		up-to-date group tables.
4:	Removed outbuf, it was used for all buffered stdout stuff,
		couldn't think of any reason to have it and it was
		BUFSIZ data space wasted.  Ditto for wbuf and tbuf.
5:	Removed superfluous call to checkmail() -- check static
		variable instead.  Also, changed so didn't find out
		the mailbox name on every iteration.

*** JUST FOR FUN ***
2:	Change "wrong flag" message to a usage message, it's more useful.
3:	Combined fidle() and contime() since they did the same thing.
4:	2.9 has loadav call, removed source from lu.c
5:	grouped all code ifdef'd for pdp11 together
6:	Put lav stuff into lu.c, whole program could run setuid if
		kmem is protected, it'll run *much* faster that way.
7:	Clear the screen on exit.

		Keith Bostic
			ARPA: keith@seismo 
			UUCP: seismo!keith
======================================================================

I have left in the LOADAV stuff in case someone ones to use it.  Just
leave in the define for LOADAV in Makefile.  If you want the load average
stuff in lu.c itself, remove this define from Makefile.  You should also
remove the define for REED.  If you are using a pdp11 put a define for
pdp11 into the Makefile.  Then just type 'make'.


				Michael Cooper
				tektronix!reed!mikec
!Funky!Stuff!
echo x - lav.c
cat >lav.c <<'!Funky!Stuff!'
/*
 * $Log:	lav.c,v $
 * Revision 1.1  84/11/10  14:45:41  mikec
 * Initial revision
 * 
 *
 */

/*
 *
 *	la.c -- Print the load average.
 *		lav must be setuid root (or appropriote uid) that can read
 *		/dev/kmem.  On some systems, such as ours, permissions to
 *		kmem are turned off for security reasons.
 *
 */

#include 	<nlist.h>
#include 	<stdio.h>

struct	nlist nl[] = {
	{ "_avenrun" },
	{ "" },
};

int	kmem;
double	avenrun[3];

main()
{
	register int i;

	if ((kmem = open("/dev/kmem", 0)) < 0) {
		fprintf(stderr, "No kmem\n");
		exit(1);
	}

	nlist("/vmunix", nl);

	if (nl[0].n_type==0) {
		fprintf(stderr, "No namelist\n");
		exit(1);
	}

	/*
	 * Print 1, 5, and 15 minute load averages.
	 * (Found by looking in kernel for avenrun).
	 */

	lseek(kmem, (long)nl[0].n_value, 0);
	read(kmem, avenrun, sizeof(avenrun));
	for (i = 0; i < (sizeof(avenrun)/sizeof(avenrun[0])); i++) {
		printf("%.2f ", avenrun[i]);
	}
	printf("\n");
}
!Funky!Stuff!
echo x - lu.c
cat >lu.c <<'!Funky!Stuff!'

/*
 ************************************************************************
 *									*
 * lu.c -- list users command.						*
 *									*
 * Version: 1.2		11/22/84					*
 *									*
 *	If lu is called without arguments, it goes into an infinite	*
 *	loop of displaying who is logged on, when, etc.			*
 *	If lu is called with a user name as an argument, it		*
 *	displays that users uid, group name, group id, home dir.,	*
 *	and the login shell.						*
 *									*
 *	lu understands the following options:				*
 *	-f filename	use 'filename' as an alternative to /etc/utmp.	*
 *	-s		don't use standout mode of terminal.		*
 *									*
 ************************************************************************
 *									*
 *	Author: Matt Giger						*
 *	Re-written by: Michael Cooper					*
 *									*
 *	Please send comments or changes you make to:			*
 *		tektronix!reed!mikec					*
 *									*
 ************************************************************************
 */

#include	<curses.h>
#include	<sys/types.h>
#include	<sys/stat.h>
#include	<signal.h>
#include	<utmp.h>
#include	<grp.h>
#include	<pwd.h>


#define EOS		'\0'

static struct utmp      U;
static char     wperm;

main (argc, argv)
int     argc;
char  **argv;
{
	register struct passwd *ppd;
	register struct group  *ggd;
	register char  *C;
	register int    sflg = TRUE;
	FILE * fp,
		*popen ();
	struct passwd  *getpwnam ();
	struct group   *getgrgid ();
	char   *ufile,
	        nbuf[9],
	       *fidle (), *ctime (), *load (), *strncpy ();
	int     x,
	        row,
	        done (), redraw ();
	long    now,
	        time ();

	for (x = 1; x < argc; x++) {
		if (argv[x][0] != '-')
			break;
		switch (argv[x][1]) {
			case 'f': 	    /* name of utmp file */
				ufile = argv[++x];
				break;
			case 's': 	    /* standout mode flag */
				sflg = 0;
				break;
			default: 
				printf ("usage: %s [-f] [-s]\n", *argv);
				exit (0);
		}
	}
	argc -= (x - 1);
	argv += (x - 1);
	if (argc > 1) {			    /* just print single user
					       information */
		for (x = 1; x < argc; x++) {
			if ((ppd = getpwnam (argv[x])) == NULL) {
				printf ("Unknown user: %s.\n", argv[x]);
				exit (0);
			}
			if ((ggd = getgrgid (ppd -> pw_gid)) == NULL) {
				printf ("Unknown group id: %s.\n", ppd -> pw_gid);
				exit (0);
			}
			printf ("%-10s  %-5d %s (%d)\t%s\t%s\n",
					ppd -> pw_name,
					ppd -> pw_uid,
					ggd -> gr_name,
					ppd -> pw_gid,
					ppd -> pw_dir,
					(ppd -> pw_shell[0] == '\0') ? "(standard sh)" :
					(strcmp (ppd -> pw_shell, "/bin/csh") == 0) ?
					"awsome csh" : ppd -> pw_shell);
		}
		exit (0);
	}
#ifndef REED
	local (FALSE);			    /* else continuous display */
#endif
	if (!(fp = fopen (ufile ? ufile : "/etc/utmp", "r"))) {
		printf ("%s: unable to open utmp %s.\n", argv[0], ufile);
		exit (0);
	}
	initscr ();
	signal (SIGINT, done);
	signal (SIGQUIT, redraw);
	for (;;) {			    /* this is beginning of continuous
					       loop */
		erase ();
		if (sflg)
			standout ();
		printw ("Name       TTY   When    Group    IDLE   Name       TTY   When    Group    IDLE \n");
		if (sflg)
			standend ();
		time (&now);
		for (row = 0; fread (&U, sizeof (U), 1, fp);) {
			if (!*U.ut_name)
				continue;
			if (!(ppd = getpwnam (strncpy (nbuf, U.ut_name, 8))))
				continue;
			ggd = getgrgid (ppd -> pw_gid);
			C = fidle (now);
			++row;
			mvprintw ((row > LINES - 2) ? (row - LINES + 2) : row,
					(row > LINES - 2) ? 41 : 0,
					"%-8.9s   %c%.-3s%7.6s %8.8s%8.8s\n",
					ppd -> pw_name,
					wperm,
					strcmp (U.ut_line, "console") ? U.ut_line + 3 : "CO",
					ctime (&U.ut_time) + 10,
					ggd ? ggd -> gr_name : "unknown",
					C);
		}
		move (LINES - 1, 0);
		time (&now);
		if (sflg)
			standout ();
		printw ("  Time:%-6.6s   %s   Users: %2d", ctime (&now) + 10, load (), row);
		switch (checkmail ()) {
			case 0: 
				printw ("         No mail.      ");
				break;
			case 1: 
				printw ("    You have mail.     ");
				break;
			case 2: 
				putchar ('\007');
				printw ("    You have new mail. ");
		}
		if (sflg)
			standend ();
		refresh ();
		rewind (fp);
	}
}

/*
 * fidle() -- deal with number of minutes idle, and check for writeability
 */
static char    *
fidle (now)
long    now;
{
	struct stat     stbuf;
	static char     buf[20];
	long    tim;

	if (!*U.ut_line)
		return ((char *) NULL);
	sprintf (buf, "/dev/%.14s", U.ut_line);
	if (stat (buf, &stbuf) == -1)
		return ((char *) NULL);
	if (stbuf.st_mode & 02)
		wperm = ' ';
	else
		wperm = '*';
	tim = (now - stbuf.st_atime + 30) / 60;
	if (tim <= 0)
		*buf = EOS;
	else
		sprintf (buf, "%3ld:%02ld", tim / 60, tim % 60);
	return (buf);
}

/*
 * checkmail() -- check to see if there is mail or if new mail has arrived.
 */
static
checkmail () 
{
	register int    ismail;
	static  off_t oldmsize = -1;
	static char    *C,
	                mailname[32];
	struct stat     statb;
	char   *getenv (), *getlogin ();

	if (oldmsize == -1 && !(C = getenv ("MAIL"))) {
		if (!(C = getenv ("USER")) && !(C = getlogin ()))
			return (0);
		sprintf (mailname, "/usr/spool/mail/%.15s", C);
		C = mailname;
	}
	if (stat (C, &statb) || !statb.st_size)
		ismail = 0;
	else
		if (oldmsize > -1 && oldmsize < statb.st_size)
			ismail = 2;
		else
			ismail = 1;
	oldmsize = statb.st_size;
	return (ismail);
}

/*
 * clean up the screen
 */
static
finish () 
{
	standend ();		    /* make sure we're not in standout mode */
	clear ();
	refresh ();
	endwin ();
}

/*
 * done() -- exit the program.
 */
static
done () 
{
	finish ();
#ifndef REED
	local (TRUE);
#endif
	signal (SIGINT, SIG_IGN);
	signal (SIGQUIT, SIG_DFL);
	exit (0);
}

/*
 * redraw() -- redraw the screen on interrupt
 */
static
redraw () 
{
	signal (SIGQUIT, SIG_IGN);
	wrefresh (curscr);
	signal (SIGQUIT, redraw);
}

/*
 * load -- figure out load average and format a string for display
 */
static char    *
load () 
{
	static char     str[40];
	double  vec[3];
	FILE * pp;
	FILE * popen ();
	char   *a,
	       *b,
	       *c;

#ifdef pdp11
	loadav (vec);
	sprintf (str, "Load Average: %.02f %.02f %.02f", vec[0], vec[1], vec[2]);
#else !pdp11 && LOADAV
	pp = popen (LOADAV, "r");
	fscanf (pp, "%f%f%f", &a, &b, &c);
	sprintf (str, "Load Average: %.02f %.02f %.02f", a, b, c);
	pclose (pp);
#else
#	include <nlist.h>

	static struct nlist nlar[] = {
				{ "_avenrun" },
				{ "" }
			};
	register int kmem;
	long lseek();

	if((kmem = open("/dev/kmem",0)) < 0) {
		finish();
		puts("Cant't access /dev/kmem.");
		done();
	}
	nlist("/vmunix", nlar);
	if(!nlar[0].n_type) {
		finish();
		puts("Can't find namelist in vmunix.");
		done();
	}
	lseek(kmem,(long)nlar[0].n_value,0);
	read(kmem,vec,sizeof(vec));
	sprintf(str,"Load Average: %.02f %.02f %.02f", vec[0],vec[1],vec[2]);
#endif pdp11
	return (str);
}

#ifndef REED
#include <sys/ioctl.h>
/*
 * local -- do local ioctl setup, especially if paging set in device driver
 */
static
local (set)
register int    set;
{
	static int      oldpage,
	                oldwidth;
	int     zero;

	if (!set) {
		ioctl (0, TIOCGSCR, &oldpage);
		zero = 0;
		ioctl (0, TIOCSSCR, &zero); /* disable CCA_PAGE */
		ioctl (0, TIOCGWID, &oldwidth);
		zero = 0;
		ioctl (0, TIOCSWID, &zero); /* disable TTYFOLD */
	}
	else {
		ioctl (0, TIOCSSCR, &oldpage);
		ioctl (0, TIOCSWID, &oldwidth);
	}
}
#endif
!Funky!Stuff!