home *** CD-ROM | disk | FTP | other *** search
- /* vi:set sw=4 ts=4: */
- #ifndef lint
- static char *sccsid = "@(#)nwho.c 3.12 rayssd!gmp 12/22/85";
- #endif lint
- /*
- ** nwho.c (version 3.12): written by
- ** Greg Paris
- ** Raytheon Submarine Signal Division
- ** Portsmouth, RI 02871
- */
-
- /*
- ** NOTE: correct operation of this program depends
- ** on the alias file being sorted by login name.
- **
- ** NOTE: the "-u utmp" option is undocumented. This
- ** program is quite memory intensive -- great for
- ** /etc/utmp use, but a real hog when used on large
- ** /usr/adm/wtmp files.
- */
-
- #include <sys/types.h>
- #include <utmp.h>
- #include <sys/stat.h>
-
- #ifdef bsd42
- #define hashosts
- #include <sys/time.h>
- #else bsd42
- #include <time.h>
- #include <sys/timeb.h>
- #endif bsd42
-
- #ifdef pyr
- /*
- ** compile in ucb universe
- ** works with ucb init being run
- */
- char utmpfl[] = "/etc/.ucbutmp"; /* for att users */
- #else pyr
- char utmpfl[] = "/etc/utmp"; /* default utmp file */
- #endif pyr
-
- char aliasfl[] = ".nwhorc"; /* default alias file */
-
- char *progname; /* program name */
- int proglen; /* program name length */
- char *requtmpfl; /* requested utmp file */
- char *reqaliasfl; /* requested alias file */
-
- extern int optind;
- extern char *optarg;
- extern int getopt();
- #ifndef EOF
- #define EOF -1
- #endif EOF
-
- int logsort(), alsort(), termsort();
- char *strcpy(), *strncpy(), *index(), *timefmt();
- char *getenv(), *malloc(), *getaliases(), *logalias();
- long gmtoffset();
-
- /*
- ** plus: structure for aliasing login names
- */
- struct plus {
- struct utmp *pu; /* pointer to utmp entry */
- char *al; /* pointer to alias */
- };
-
- struct utmp dumutmp; /* for SZ defines */
-
- #ifndef TTYLEN
- #define TTYLEN 2 /* assume ttyxx */
- #endif TTYLEN
- #define MINENTLEN (TTYLEN+7) /* space for term and time */
- #define MINSPACES 2 /* min between columns */
- #define WHITESPACE case ' ': case '\t' /* white space in alias file */
- #define DEFLINELEN 79 /* avoids premature wrapping */
-
- #define SZ_UTMP sizeof(struct utmp)
- #define SZ_PLUS sizeof(struct plus)
- #define SZ_UTNAME sizeof(dumutmp.ut_name)
-
- #ifdef hashosts
- #define SZ_UTHOST sizeof(dumutmp.ut_host)
- #endif hashosts
-
- /*
- ** add characters to output buffer
- */
- #define bufadd(str) for(fr = str;*fr;*pb++ = *fr++)
- #define bufaddn(st, nn) for(fr = st, i = nn;*fr && i--;*pb++ = *fr++)
-
- /*
- ** add TTYLEN-letter abbreviation for terminal name
- ** loses if non-tty devices named t* exist
- */
- #define buftty(tty) if(tty[0] == 't') bufadd(tty+3); \
- else bufaddn(tty, TTYLEN)
-
- /*
- ** add " hh:mm "
- */
- #define buftime(tm) bufadd(timefmt(tm -= offset))
-
- /*
- ** add alias or login name
- */
- #define bufname(ppp) \
- if(ppp->al) bufadd(ppp->al); \
- else bufaddn(ppp->pu->ut_name, SZ_UTNAME)
-
- #ifdef hashosts
- /*
- ** add " (hostname)"
- */
- #define bufhost(ppp) \
- if(hosts && ppp->pu->ut_host[0]) { \
- *pb++ = ' '; *pb++ = '('; \
- bufaddn(ppp->pu->ut_host, SZ_UTHOST); \
- *pb++ = ')'; }
- #endif hashosts
-
- #ifdef NOERRMSGS
- #define errexit(code,msg) exit(code)
- #else NOERRMSGS
- #define errexit(code,msg) \
- static char errmsg[] = msg; \
- (void) write(2, progname, proglen); \
- (void) write(2, errmsg, sizeof(errmsg) - 1); \
- exit(code)
- #endif NOERRMSGS
-
- main(ac, av)
- int ac;
- char *av[];
- {
- register struct plus *pp, *ppp;
- register int entries;
- register char *pb, *fr;
- register int i;
- struct plus *ppmax;
- struct utmp *pu, *puu;
- int size;
- int linelen, entrylen, spaces, cols;
- int termord, quick, colreq;
- char *linebuf, *aliasbuf, *entryend;
- long offset;
- int hosts;
-
- proglen = strlen(progname = av[0]);
- requtmpfl = reqaliasfl = (char *) 0;
- hosts = termord = colreq = linelen = quick = 0;
-
- while((i = getopt(ac, av, "hqta:l:c:u:")) != EOF) {
- switch(i) {
- case 'h': /* show remote host field */
- hosts = 1;
- break;
- case 'a': /* alias file request */
- reqaliasfl = optarg;
- break;
- case 'u': /* utmp file request */
- requtmpfl = optarg;
- break;
- case 'q': /* don't expand */
- quick = 1;
- break;
- case 'l': /* line length */
- if(*optarg == 't') {
- linelen = termlen(optarg);
- } else if((linelen = atoi(optarg)) < MINENTLEN + 2) {
- errexit(1, ": improper line length\n");
- }
- break;
- case 'c': /* set columns */
- if((colreq = atoi(optarg)) < 1) {
- errexit(1, ": improper column count\n");
- }
- break;
- case 't': /* terminal order output */
- termord = 1;
- break;
- case '?': /* bad option */
- default: /* error */
- usage();
- }
- }
-
- offset = gmtoffset();
- linelen = linelen ? linelen : DEFLINELEN;
-
- if((i = open(requtmpfl ? requtmpfl : utmpfl, 0)) < 0) {
- errexit(1, ": can't open utmp file\n");
- }
-
- if((entries = (size = fdsize(i))/SZ_UTMP) == 0) {
- /*
- ** no users
- */
- exit(0);
- }
- if(size != entries * SZ_UTMP) {
- errexit(2, ": bad utmp file\n");
- }
-
- puu = pu = (struct utmp *) malloc((unsigned) size);
- ppp = pp = (struct plus *) malloc((unsigned) (entries * SZ_PLUS));
- if(!pu || !pp) {
- errexit(2, ": no memory for utmp entries\n");
- }
- if(read(i, (char *) pu, size) != size) {
- errexit(2, ": read error on utmp file\n");
- }
- (void) close(i);
-
- /*
- ** initialize plus entries, ignoring null utmp entries, then sort
- */
- puu += entries; /* start at end */
- entries = 0; /* forget... */
- while(puu-- > pu) { /* easier to go backwards */
- if(puu->ut_name[0]) { /* active entry */
- ppp->al = (char *) 0; /* no alias yet */
- (ppp++)->pu = puu; /* set utmp pointer */
- ++entries; /* active count */
- }
- }
- ppmax = ppp; /* at end */
-
- if(entries == 0) {
- /*
- ** there were inactive utmp entries,
- ** but no active entries (yes, it happens)
- */
- exit(0);
- }
-
- if(!quick || !termord) {
- /*
- ** sort by login necessary for alias expansion
- ** default if terminal order not requested
- */
- qsort((char *) pp, entries, SZ_PLUS, logsort);
- }
-
- /*
- ** if alias file, expand aliases
- */
- if(!quick && (aliasbuf = getaliases())) {
- /*
- ** expand aliases for login names
- ** found in the alias file
- */
- expand(pp, ppmax, aliasbuf);
-
- if(termord) {
- /*
- ** put back in terminal order
- */
- qsort((char *) pp, entries, SZ_PLUS, termsort);
- } else {
- /*
- ** sort by name to be printed
- ** (alias if one, login otherwise)
- */
- qsort((char *) pp, entries, SZ_PLUS, alsort);
- }
- }
-
- /*
- ** take extreme pains to format the output
- ** variable number of columns
- ** ragged right (Raytheon standard)
- ** uses as much of line as possible
- ** minimum character output (no tabs)
- ** at least MINSPACES spaces between columns
- ** sorted alphabetically by user name down columns
- ** single write
- */
- entrylen = 0;
- for(ppp = pp;ppp < ppmax;++ppp) {
- if(ppp->al) {
- /*
- ** check alias length
- */
- i = strlen(ppp->al);
- } else {
- /*
- ** check login length
- */
- pb = ppp->pu->ut_name;
- for(i = 0;*pb++ && i < SZ_UTNAME;++i)
- ;
- }
-
- #ifdef hashosts
- /*
- ** add room for remote host
- */
- if(hosts && ppp->pu->ut_host[0]) {
- size = i + 3; /* room for parens and space */
- pb = ppp->pu->ut_host;
- for(i = 0;*pb++ && i < SZ_UTHOST;++i)
- ;
- i += size;
- }
- #endif hashosts
-
- entrylen = (entrylen < i) ? i : entrylen;
- }
-
- if(colreq == 1 || entries == 1) {
- /*
- ** single-column is always valid
- */
- cols = 1;
- } else {
- /*
- ** here's the basic equation for formatting
- ** linelen = (cols - 1) * (entrylen + spaces) + entrylen
- ** solve for columns using
- ** entrylen = maxnamelen + MINENTLEN
- ** spaces = MINSPACES
- ** then solve for entrylen with
- ** spaces = MINSPACES
- ** then solve for spaces
- */
- spaces = MINSPACES;
- entrylen += MINENTLEN;
-
- if(linelen <= entrylen) {
- cols = 1;
- } else {
- cols = 1 + (linelen - entrylen) / (entrylen + spaces);
- }
-
- /*
- ** if there is a column request
- ** check it's validity
- */
- if(colreq && cols > colreq) {
- /*
- ** use number of columns requested
- */
- cols = colreq;
- }
- }
-
- if(cols == 1) {
- /*
- ** simpler processing for single-column mode
- */
- if(!(pb = linebuf = malloc((unsigned)(entries * entrylen)))) {
- errexit(2, ": no memory for output buffer\n");
- }
-
- for(ppp = pp;ppp < ppmax;++ppp) {
- entryend = pb + linelen;
-
- buftty(ppp->pu->ut_line);
- buftime(ppp->pu->ut_time);
- bufname(ppp);
- #ifdef hashosts
- bufhost(ppp);
- #endif hashosts
-
- /*
- ** truncate if necessary, add newline
- */
- pb = (pb >= entryend) ? (entryend - 1) : pb;
- *pb++ = '\n';
- }
- } else {
- int lines, linenum, colnum, lastcol, maxcols;
-
- /*
- ** multi-column mode
- ** (can't have more columns than entries)
- */
- cols = (cols > entries) ? entries : cols;
- entrylen = (linelen - (cols - 1) * spaces) / cols;
- spaces = (linelen - cols * entrylen) / (cols - 1);
- lines = entries / cols;
- if(lastcol = entries % cols) {
- ++lines;
- } else {
- lastcol = cols;
- }
-
- if(!(pb = linebuf = malloc((unsigned) (lines * linelen)))) {
- errexit(2, ": no memory for output buffer\n");
- }
-
- maxcols = cols;
- for(linenum = 0;linenum < lines;) {
- ppp = pp + linenum;
- for(colnum = 0;;) {
- entryend = pb + spaces + entrylen;
-
- buftty(ppp->pu->ut_line);
- buftime(ppp->pu->ut_time);
- bufname(ppp);
- #ifdef hashosts
- bufhost(ppp);
- #endif hashosts
-
- if(++colnum < maxcols) {
- ppp += (colnum <= lastcol) ? lines : (lines - 1);
- while(pb < entryend) {
- *pb++ = ' ';
- }
- continue;
- }
- break;
- }
-
- *pb++ = '\n';
- if(++linenum == lines - 1) {
- maxcols = lastcol;
- }
- }
- }
-
- size = (int) (pb - linebuf);
- if(write(1, linebuf, size) != size) {
- errexit(2, ": output write error\n");
- }
-
- exit(0);
- }
-
- /*
- ** logsort: sort plus entries by login first, time second
- */
- logsort(pp0, pp1)
- struct plus *pp0;
- struct plus *pp1;
- {
- int val;
-
- /*
- ** string compare
- */
- if(val = strncmp(pp0->pu->ut_name, pp1->pu->ut_name, SZ_UTNAME)) {
- return(val);
- }
-
- /*
- ** chronological
- */
- return((int) (pp0->pu->ut_time - pp1->pu->ut_time));
- }
-
- /*
- ** alsort: sort plus entries by alias or ut_name first, time second
- */
- alsort(pp0, pp1)
- struct plus *pp0;
- struct plus *pp1;
- {
- int val;
-
- /*
- ** string compare
- */
- if(pp0->al && pp1->al) {
- /*
- ** both have aliases -- easy
- */
- if(val = strcmp(pp0->al, pp1->al)) {
- return(val);
- }
- } else {
- /*
- ** one or both missing alias
- */
- if(val = strncmp(
- (pp0->al ? pp0->al : pp0->pu->ut_name), /* arg0 */
- (pp1->al ? pp1->al : pp1->pu->ut_name), /* arg1 */
- SZ_UTNAME)
- ) {
- return(val);
- }
-
- /*
- ** different lengths?
- ** assume one with alias is longer
- ** (fast but possibly incorrect?)
- */
- if(val = (int) (pp0->al - pp1->al)) {
- return(val);
- }
- }
-
- /*
- ** chronological
- */
- return((int) (pp0->pu->ut_time - pp1->pu->ut_time));
- }
-
- /*
- ** termsort: sort plus entries by terminal order
- */
- termsort(pp0, pp1)
- struct plus *pp0;
- struct plus *pp1;
- {
- return((int)(pp0->pu - pp1->pu));
- }
-
- /*
- ** getaliases: read alias file into dynamic buffer
- */
- char *
- getaliases()
- {
- char *s;
- char *b;
- int fd;
- int size;
-
- if(reqaliasfl) {
- /*
- ** open requested alias file -- must be readable
- */
- if((fd = open(reqaliasfl, 0)) < 0) {
- errexit(1, ": can't open alias file\n");
- }
- } else {
- /*
- ** try to open default alias file
- */
- if(!(b = getenv("HOME"))) {
- return((char *) 0);
- }
-
- if(!(s = malloc(
- (unsigned) ((size = strlen(b)) + 1 + sizeof(aliasfl))))
- ) {
- errexit(2, ": no memory for path name expansion\n");
- }
-
- (void) strcpy(s, b);
- *(s+size++) = '/';
- (void) strcpy(s+size, aliasfl);
-
- if((fd = open(s, 0)) < 0) {
- /*
- ** can't open for read -- not error
- */
- free(s);
- return((char *) 0);
- }
- free(s);
- }
-
- if((size = fdsize(fd)) < 4) {
- /*
- ** smaller than smallest possible alias -- ignore
- */
- (void) close(fd);
- return((char *) 0);
- }
-
- /*
- ** get null-terminated buffer
- */
- if(!(b = malloc((unsigned) (size + 1)))) {
- errexit(2, ": no memory for alias buffer\n");
- }
- *(b+size) = '\0';
-
- if(read(fd, b, size) != size) {
- errexit(2, ": read error on alias file\n");
- }
- (void) close(fd);
-
- return(b);
- }
-
- /*
- ** fdsize: return size of file corresponding to fd
- */
- fdsize(fd)
- int fd;
- {
- struct stat st;
-
- if(fstat(fd, &st) < 0) {
- errexit(2, ": fstat failed in fdsize\n");
- }
-
- return((int) st.st_size);
- }
-
- /*
- ** expand: expand alias names
- */
- expand(ppp, ppmax, buf)
- register struct plus *ppp;
- register struct plus *ppmax;
- register char *buf;
- {
- register int next;
- register int cmp;
- char *login;
- char *alias;
-
- next = 1;
- while(ppp < ppmax) {
- if(next) {
- if(!(buf = logalias(buf, &login, &alias))) {
- /*
- ** no more list entries
- */
- return;
- }
- }
-
- if((cmp = strncmp(login, ppp->pu->ut_name, SZ_UTNAME)) < 0) {
- /*
- ** no match, keep utmp entry, continue in list
- */
- next = 1;
- continue;
- }
-
- if(cmp > 0) {
- /*
- ** no match, next utmp entry, too far in list
- */
- ++ppp;
- next = 0;
- continue;
- }
-
- /*
- ** matched, next utmp entry, try to match this list entry again
- */
- (ppp++)->al = alias;
- next = 0;
- }
- }
-
- /*
- ** logalias: get pointer to login and alias
- ** return NULL if end of buffer, new position otherwise
- ** buf must point to first character in line
- */
- char *
- logalias(buf, plogin, palias)
- register char *buf;
- char **plogin;
- char **palias;
- {
- register int inword;
-
- inword = 0;
- while(!inword) { /* find beginning of login */
- switch(*buf) {
- case '\r': /* empty lines -- OK */
- case '\n': /* empty lines -- OK */
- WHITESPACE: /* white space at beginning */
- ++buf;
- break;
- case '\0': /* end of buffer */
- return((char *) 0);
- default: /* found login */
- inword = 1;
- *plogin = buf; /* save login */
- break;
- }
- }
-
- while(inword) { /* find end of login */
- switch(*buf) {
- WHITESPACE: /* end of login */
- inword = 0;
- *buf++ = '\0'; /* null terminate */
- break;
- case '\0': /* end of buffer */
- return((char *) 0);
- default: /* still in login */
- ++buf;
- break;
- }
- }
-
- while(!inword) { /* find beginning of alias */
- switch(*buf) {
- WHITESPACE: /* still in white space */
- ++buf;
- break;
- case '\0': /* end of buffer */
- return((char *) 0);
- default: /* beginning of alias */
- inword = 1;
- *palias = buf; /* save alias */
- break;
- }
- }
-
- while(inword) { /* find end of alias */
- switch(*buf) {
- case '\r': /* end of line */
- case '\n': /* end of line */
- inword = 0; /* end of alias */
- *buf++ = '\0'; /* null terminate */
- break;
- case '\0': /* end of buffer */
- inword = 0; /* end of alias */
- break; /* leave buffer here (OK) */
- default: /* still in alias */
- if(*buf < ' ' || *buf > '~') { /* ASCII */
- /*
- ** replace control characters
- ** (they screw up output format)
- */
- *buf = ' '; /* replace with space */
- }
- ++buf;
- break;
- }
- }
-
- return(buf); /* return buffer position */
- }
-
- /*
- ** usage: print a usage message and exit
- */
- usage()
- {
- static char preamble[] = "usage -- ";
- static char postamble[] =
- " [-q] [-t] [-c columns] [-l length] [-a aliasfl]\n";
-
- #ifdef hashosts
- static char hostamble[] = " [-h]";
- #endif hashosts
-
- (void) write(2, preamble, sizeof(preamble) - 1);
- (void) write(2, progname, proglen);
- #ifdef hashosts
- (void) write(2, hostamble, sizeof(hostamble) - 1);
- #endif hashosts
- (void) write(2, postamble, sizeof(postamble) - 1);
-
- exit(1);
- }
-
- /*
- ** gmtoffset: get seconds offset from gmt
- */
- long
- gmtoffset()
- {
- #ifdef bsd42
-
- struct timeval tv;
- struct timezone tz;
- struct tm *pt;
- struct tm *localtime();
-
- (void) gettimeofday(&tv, &tz);
- pt = localtime((time_t *) &tv.tv_sec);
-
- return(tz.tz_minuteswest * 60 - (pt->tm_isdst ? 3600 : 0));
-
- #else bsd42
-
- struct timeb tb;
- struct tm *pt;
- struct tm *localtime();
-
- (void) ftime(&tb);
- pt = localtime(&tb.time);
-
- return((long) tb.timezone * 60 - (pt->tm_isdst ? 3600 : 0));
-
- #endif bsd42
- }
-
- /*
- ** timefmt: format the time string
- ** this routine is ASCII dependent
- */
- char *
- timefmt(tim)
- long tim;
- {
- static char tbf[8];
- int t;
- int tens;
-
- (void) strcpy(tbf, " 00:00 "); /* initialize */
-
- if((t = (tim / 3600) % 24) > 9) { /* hours */
- tbf[1] = (tens = t / 10) + '0';
- tbf[2] = t - (tens * 10) + '0'; /* mult faster than mod */
- } else {
- tbf[1] = ' '; /* leading space */
- tbf[2] = t + '0';
- }
-
- if((t = (tim / 60) % 60) > 9) { /* minutes */
- tbf[4] = (tens = t / 10) + '0';
- tbf[5] = t - (tens * 10) + '0'; /* mult faster than mod */
- } else {
- tbf[4] = '0'; /* leading zero */
- tbf[5] = t + '0';
- }
-
- return(tbf);
- }
-
- /*
- ** termlen: get line length from termcap
- ** if arg contains '=' use following as
- ** terminal name, else use $TERM
- */
- termlen(arg)
- char *arg;
- {
- char *name;
- char tbuf[1024];
- int len;
-
- if((name = index(arg, '=')) == (char *)0) {
- name = getenv("TERM");
- } else {
- ++name;
- }
-
- if(name != (char *)0) {
- if(tgetent(tbuf, name) == 1) {
- if((len = tgetnum("co")) > 0) {
- return(tgetflag("am") ? len - 1 : len); /* avoid wrap */
- }
- }
- }
-
- return(DEFLINELEN);
- }
-