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