231b3679@fergvax.unl.edu (Mike Gleason) (04/30/91)
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@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 */
jwz@lucid.com (Jamie Zawinski) (04/30/91)
I'd just like to point out that GNU Finger encapsulates most of the functionality being bandied about in these "who's on" programs being posted here. It will also tell you which of your local machines a particular person is logged on to, and how long. And it does faces. It's way cool. You can ftp it from export.lcs.mit.edu. -- Jamie
simon@liasun2.epfl.ch (Simon Leinen) (04/30/91)
In article <JWZ.91Apr30012333@thalidomide.lucid.com> jwz@lucid.com (Jamie Zawinski) writes: I'd just like to point out that GNU Finger encapsulates most of the functionality being bandied about in these "who's on" programs being posted here ... You can ftp it from export.lcs.mit.edu. I think this should be `prep.ai.mit.edu', directory /pub/gnu, file `finger-1.0b.tar.Z' (175734 bytes). Have fun, -- Simon.
dcr5w@bayes.math.Virginia.EDU (David C Royster) (04/30/91)
In article <JWZ.91Apr30012333@thalidomide.lucid.com> jwz@lucid.com (Jamie Zawinski) writes: >I'd just like to point out that GNU Finger encapsulates most of the >functionality being bandied about in these "who's on" programs being >posted here. It will also tell you which of your local machines a >particular person is logged on to, and how long. And it does faces. >It's way cool. You can ftp it from export.lcs.mit.edu. WHO GNU ?