whom.c - another who (and whoson) replacement
Mike Gleason
231b3679 at fergvax.unl.edu
Tue Apr 30 16:10:46 AEST 1991
I had also been working on a 'who' program. I tried yesterday's posting,
whoson, but it didn't work because my unix host uses fopen(file, "r") for
both binary and text files, and the author of whoson used "rb". Anyway, you
may want to try this out. Along with the usual stuff 'who' prints out, it
also prints the user's full names. You could get this effect by just typing
'finger', but this program is much, much faster. Whom will also tell you
which tty's are writeable (so you can phone, talk, or write them) by denoting
them with an asterisk. Here is a sample of the output:
User: TTY: Where: Time On: Idle: Name:
252u3693 16* 3:32 0 Brian Guenther
tdavis p1 mod801.unomaha.e 0:47 Thomas Davis
252u3744 p2* tsx-wsec103.unl. 0:34 Jerry Annin
gunnit p3 mandala.unl.edu 1:41 1:31 Gunnit S. Khurana
231b3630 p4* tsx-wsec104.unl. 1:50 35 Peter Fields
chaddan p5* tsx-wsec103.unl. 0:40 Christopher Neal Haddan
I have spent way too much time optimizing this silly thing, so it is quite
quick. If you use it, please let me know, and I'll keep working on it.
---------cut here--------
/*
* NCEMRS whom (C) 1991 Mike Gleason, NCEMRSoft.
* version 1.0 -- 08 Mar 91
* version 2.0 -- 18 Apr 91
* version 2.1 -- 29 Apr 91
* Compile with CC for the smallest executable.
* Until 10 May 91, I am emailable at 231b3679 at fergvax.unl.edu.
*/
#include <stdio.h>
#include <time.h>
#include <string.h>
#ifdef THINK_C
#include "utmp.h"
#include <stdlib.h>
#include "stat.h"
#else
#include <utmp.h>
#include <sys/types.h>
#include <sys/stat.h>
#endif
/* The only things you should need to configure are:
* PASSWORD_FILE
* DFILE
* READ_BINARY
* WRITE_BINARY
*/
/* I developed this using a Macintosh and Think C, and it needs the
binary mode specifier char. I think all non-unix machines will
need that too, but I doubt this will be run on a non-unix machine
anyway. */
#ifdef THINK_C
#define WRITE_BINARY "wb"
#define READ_BINARY "rb"
#else
#define WRITE_BINARY "w" /* On my unix host, you don't use the b's */
#define READ_BINARY "r"
#endif
#define ADD_SPACE *lyne++ = ' '
#define PUBLIC_WRITE_PERM 00002 /* write permission: other */
#ifndef NO_REAL_NAMES
/* One of whom's best features is that it can print out the real name
of each user along with the other stuff. There are a few minor
disadvantages. (1), it needs to munge through the password file,
and (2) it needs to keep it's own private data file on disk somewhere.
If your password file does not have the real names of the users
in it, or if you can't spare the disk space (but it's not THAT big),
or just don't like it, then define NO_REAL_NAMES. The reason for
the private datafile is because Whom uses an optimized version of
the password file so it rapidly find information about any user.
If it didn't have this file, you might as well use finger, because
it will take forever to do this otherwise. */
/* Point me to the location of your system's password file. I need that
some stuff I need to make the data file, namely the login and real
name of each user. */
#define PASSWORD_FILE "/etc/passwd"
/* Point me to where you want to store the data file. The size of this
will vary on the total number of users, but my machine has about 700
users with only a 30k data file. The data file will be sorted and
each record will be the same size so we can use lightning fast
searching. Programs like finger and getpwnam() probably have to
search the entire passwd file every time through because it isn't
sorted, and each line is of variable length. For my machine it could
take up to 700 searches just to find one user with those programs,
but for this little ditty, it would only take a maximum of 10
searches. The ideal path for this file would be in the /etc direcotry
along with the passwd file, but my system admins don't like me
writing stuff there! */
#define DFILE "/u3/231b3679/.whom"
#define MAX_NAME_LEN 30
#define MAX_LOGIN_LEN 8
#define COLONS_TO_SKIP 4 /* Skip this many fields in passwd file */
typedef struct
{
char login[MAX_LOGIN_LEN + 2]; /* leave room for ' \0'*/
char name[MAX_NAME_LEN + 2]; /* leave room for ' \0' */
} User;
#endif
/* These should be declared in utmp.h, but I've found an exception so... */
#ifndef UTMP_FILE
#define UTMP_FILE "/etc/utmp"
#endif
#ifndef WTMP_FILE
#define WTMP_FILE "/usr/adm/wtmp"
#endif
/* Bonus Features:
* 1. #define ANSI, and when whom does it's thang, it will first
* clear the screen and print the header line in boldface (oooh).
*
* 2. When running the program, if you pass an arbitrary argument
* (ex. whom -useWtmpDude) will try to use the wtmp file.
*/
/* Prototypes */
extern void *bsearch();
extern void *malloc();
char *strnpcat (/* dst, src, howMany */);
/* main, finally! */
int main (argc, argv)
int argc;
char **argv;
{
FILE *in, *dev;
char fname[31], dname[31], str[31];
char *lyne, line[128];
struct utmp info;
short wtmp, writeable;
time_t Now;
int result;
struct stat stbuf;
#ifndef NO_REAL_NAMES
User *Users, *u;
long numUsers;
#endif
if ((wtmp = (argc != 1)))
strcpy (fname, WTMP_FILE);
else
strcpy (fname, UTMP_FILE);
if (!(in = fopen (fname, READ_BINARY)))
{
fprintf (stderr, "%s: Could not open the file \"%s\".\n", argv[0], fname
);
exit (1);
}
#ifndef NO_REAL_NAMES
result = OpenDataFile (&Users, &numUsers);
if (result < 0)
{
Thrash ();
result = OpenDataFile (&Users, &numUsers);
}
#ifndef ANSI
printf ("\nUser: TTY: Where: Time On: Idle: %s\n",
result==0 ? "Name:" : "");
#else
/* clear screen, home cursor, and turn bold face on. */
printf ("\n\033[2J \033[H\033[1m");
printf ("User: TTY: Where: Time On: Idle: %s\033[0m\n",
result==0 ? "Name:" : "");
#endif
#else
#ifndef ANSI
printf ("\nUser: TTY: Where: Time On: Idle:\n");
#else
/* clear screen, home cursor, and turn bold face on. */
printf ("\n\033[2J \033[H\033[1m");
printf ("User: TTY: Where: Time On: Idle:\033[0m\n");
#endif
#endif
(void) time (&Now);
while ((fread (&info, (long) sizeof (info), 1, in)) == 1L)
{
if (!*info.ut_name)
continue;
lyne = (char *) line;
*lyne = '\0';
lyne = strnpcat (lyne, info.ut_name, 8L);
ADD_SPACE; ADD_SPACE;
writeable = 0;
strcpy (dname, "/dev/");
strcat (dname, info.ut_line); /* form the full path of tty */
stat (dname, &stbuf);
writeable = stbuf.st_mode | PUBLIC_WRITE_PERM;
if (*info.ut_line == 't') /* is it in the form ttyxx? */
{
/* This assumes that after 'tty', there are only two other
characters. */
if (writeable)
info.ut_line[5] = '*';
lyne = strnpcat (lyne, info.ut_line+3, 3L);
}
else
lyne = strnpcat (lyne, info.ut_line, 3L);
/* else its probably 'console' */
ADD_SPACE; ADD_SPACE;
lyne = strnpcat (lyne, info.ut_host, 16L);
ADD_SPACE; ADD_SPACE; ADD_SPACE;
TimeOn (Now - info.ut_time, str);
lyne = strnpcat (lyne, str, 10L);
IdleTime ((Now - stbuf.st_mtime), str);
lyne = strnpcat (lyne, str, 7L);
#ifndef NO_REAL_NAMES
if (result == 0)
{
info.ut_name[8] = '\0';
u = (User *) bsearch (info.ut_name, Users, numUsers,
(long) sizeof (User), strcmp);
if (u)
strcpy (str, u->name);
else
strcpy (str, "(Unknown)");
lyne = strnpcat (lyne, str, 28L);
}
#endif
puts (line); /* finally, dump the whole line to stdout */
}
fputc ('\n', stdout);
fclose (in);
} /* main */
/* strnpcat: given two strings, this function will copy up to 'howMany'
characters to the destination. If the source string is shorter than
'howMany' characters, it will pad the destinaton string with spaces
until it's length is howMany. In addition, this will also return the
pointer where we left off. It'd be silly to use strcat all the time,
since every time you called strcat it would have to loop through the
whole string just to find the end. */
char *strnpcat (dst, src, howMany)
register char *dst, *src;
long howMany;
{
register int echoSpaces;
for (echoSpaces = 0; howMany > 0; dst++, src++, --howMany)
{
if (!*src)
echoSpaces = 1;
if (echoSpaces)
*dst = ' ';
else
*dst = *src;
}
*dst = '\0';
return (dst);
} /* strnpcat */
int TimeOn (tyme, tstr)
long tyme;
char *tstr;
{
long hr, min, day;
tyme /= 60L;
day = tyme / 1440L;
hr = (tyme - day * 1440L) / 60L;
min = (tyme - (day * 1440L) - (hr * 60L));
sprintf (tstr, "%ld:%02ld", hr, min);
} /* TimeOn */
int IdleTime (tyme, tstr)
long tyme;
char *tstr;
{
long hr, min, day;
if (((tyme + 30L) / 60L) > 0L)
{
tyme /= 60L;
day = tyme / 1440L;
hr = (tyme - day * 1440L) / 60L;
min = (tyme - (day * 1440L) - (hr * 60L));
if (hr > 0L)
sprintf (tstr, "%ld:%02ld", hr, min);
else
sprintf (tstr, "%ld", min);
}
else *tstr = '\0';
} /* IdleTime */
#ifndef NO_REAL_NAMES
int OpenDataFile (Users, numUsers)
User **Users;
long *numUsers;
{
FILE *data;
if (!(data = fopen (DFILE, READ_BINARY)))
return (-1);
if (fread (numUsers, (long) sizeof (*numUsers), 1L, data) != 1L)
return (1);
*Users = (User *) malloc ((long) sizeof (User) * (*numUsers));
if (!*Users)
return (2);
if (fread (*Users, (long) sizeof (User) * (*numUsers), 1L, data) != 1L)
return (3);
fclose (data);
return (0); /* noErr */
} /* OpenDataFile */
/* Thrash: This creates the data file, which is needed so you can print
the real names of the users along with their logins. This is
extremely useful on machines (like mine) whose logins are mostly
numbers or alphanumeric garbage. */
int Thrash ()
{
FILE *passwd, *out;
long nUsers, i;
char lyne[256];
User *users;
int skippedcolons, j;
register char *p, *q;
passwd = fopen (PASSWORD_FILE, "r");
if (!passwd)
return (0);
nUsers = 0L;
while (fgets (lyne, (int) sizeof (lyne), passwd))
nUsers++;
rewind (passwd);
users = (User *) calloc (nUsers, (long) sizeof (User));
if (!users)
return (0);
for (i = 0; fgets (lyne, (int) sizeof (lyne), passwd); i++)
{
p = lyne;
q = users[i].login;
while (*p != ':') /* get login */
*q++ = *p++;
*q = '\0'; /* add terminating null */
/* After getting the login, skip over the fields in between the
login and real name (encoded password, userid, groupid). */
skippedcolons = 0;
while (*p)
{
if (*p++ == ':')
skippedcolons++;
if (skippedcolons >= COLONS_TO_SKIP)
break;
}
if (skippedcolons < COLONS_TO_SKIP)
return (0);
/* Copy stuff until we hit a colon or a comma. I only want to
keep the names, and on our machine at least, after the real
name there are commas followed by junk like office address
and such ("Herbie H. Husker,104 Nebraska Union,2-3970").
This example would only copy "Herbie H. Husker" to the name
field. */
for (j = 0, q = users[i].name;
(*p && j < MAX_NAME_LEN && *p != ':' && *p != ',');
p++, q++, j++)
{
*q = *p;
}
*q = '\0'; /* add null terminator */
}
fclose (passwd);
qsort (users, i, (long) sizeof (User), strcmp);
out = fopen (DFILE, WRITE_BINARY);
if (!out)
return (0);
if (fwrite (&i, (long) sizeof (i), 1L, out) != 1L)
return (0);
if (fwrite (users, (long) sizeof (User) * i, 1L, out) != 1L)
return (0);
fclose (out);
return (1);
} /* Thrash */
#endif
/* eof */
More information about the Alt.sources
mailing list