Home | History | Annotate | Download | only in acct
      1 /*
      2  * CDDL HEADER START
      3  *
      4  * The contents of this file are subject to the terms of the
      5  * Common Development and Distribution License (the "License").
      6  * You may not use this file except in compliance with the License.
      7  *
      8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
      9  * or http://www.opensolaris.org/os/licensing.
     10  * See the License for the specific language governing permissions
     11  * and limitations under the License.
     12  *
     13  * When distributing Covered Code, include this CDDL HEADER in each
     14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     15  * If applicable, add the following below this CDDL HEADER, with the
     16  * fields enclosed by brackets "[]" replaced with your own identifying
     17  * information: Portions Copyright [yyyy] [name of copyright owner]
     18  *
     19  * CDDL HEADER END
     20  */
     21 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
     22 /*	  All Rights Reserved  	*/
     23 
     24 
     25 /*
     26  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
     27  * Use is subject to license terms.
     28  */
     29 #pragma ident	"%Z%%M%	%I%	%E% SMI"
     30 
     31 
     32 /*
     33  *	acctcon [-l file] [-o file] <wtmpx-file
     34  *	-l file	causes output of line usage summary
     35  *	-o file	causes first/last/reboots report to be written to file
     36  *	reads input (normally /var/adm/wtmpx), produces
     37  *	list of sessions, sorted by ending time in tacct.h format
     38  */
     39 
     40 #include <stdio.h>
     41 #include <sys/types.h>
     42 #include <sys/param.h>
     43 #include "acctdef.h"
     44 #include <ctype.h>
     45 #include <time.h>
     46 #include <utmpx.h>
     47 #include <locale.h>
     48 #include <string.h>
     49 #include <search.h>
     50 #include <stdlib.h>
     51 
     52 int   a_tsize = A_TSIZE;
     53 int	tsize	= -1;	/* highest index of used slot in tbuf table */
     54 static	int csize;
     55 struct  utmpx	wb;	/* record structure read into */
     56 struct	ctmp	cb;	/* record structure written out of */
     57 struct	tacct	tb;
     58 double	timet, timei;
     59 
     60 struct tbuf {
     61 	char	tline[LSZ];	/* /dev/...  */
     62 	char	tname[NSZ];	/* user name */
     63 	time_t	ttime;		/* start time */
     64 	dev_t	tdev;		/* device */
     65 	int	tlsess;		/* # complete sessions */
     66 	int	tlon;		/* # times on (ut_type of 7) */
     67 	int	tloff;		/* # times off (ut_type != 7) */
     68 	long	ttotal;		/* total time used on this line */
     69 } *tbuf;
     70 
     71 struct ctab {
     72 	uid_t		ct_uid;
     73 	char		ct_name[NSZ];
     74 	long 		ct_con[2];
     75 	ushort_t	ct_sess;
     76 } *pctab;
     77 
     78 int	nsys;
     79 struct sys {
     80 	char	sname[LSZ];	/* reasons for ACCOUNTING records */
     81 	char	snum;		/* number of times encountered */
     82 } sy[NSYS];
     83 
     84 static char time_buf[50];
     85 time_t	datetime;	/* old time if date changed, otherwise 0 */
     86 time_t	firstime;
     87 time_t	lastime;
     88 int	ndates;		/* number of times date changed */
     89 int	exitcode;
     90 char	*report	= NULL;
     91 char	*replin = NULL;
     92 
     93 uid_t	namtouid();
     94 dev_t	lintodev();
     95 static int valid(void);
     96 static void fixup(FILE *);
     97 static void loop(void);
     98 static void bootshut(void);
     99 static int iline(void);
    100 static void upall(void);
    101 static void update(struct tbuf *);
    102 static void printrep(void);
    103 static void printlin(void);
    104 static int tcmp(struct tbuf *, struct tbuf *);
    105 static int node_compare(const void *, const void *);
    106 static void enter(struct ctmp *);
    107 static void print_node(const void *, VISIT, int);
    108 static void output(void);
    109 
    110 extern char 	*optarg;
    111 extern int	optind;
    112 
    113 void **root = NULL;
    114 
    115 int
    116 main(int argc, char **argv)
    117 {
    118 	int c;
    119 
    120 	(void) setlocale(LC_ALL, "");
    121 	while ((c = getopt(argc, argv, "l:o:")) != EOF)
    122 		switch (c) {
    123 		case 'l':
    124 			replin = optarg;
    125 			break;
    126 		case 'o':
    127 			report = optarg;
    128 			break;
    129 		case '?':
    130 			fprintf(stderr, "usage: %s [-l lineuse] "
    131 			    "[-o reboot]\n", argv[0]);
    132 			exit(1);
    133 		}
    134 
    135 	if ((tbuf = (struct tbuf *)calloc(a_tsize,
    136 		sizeof (struct tbuf))) == NULL) {
    137 		fprintf(stderr, "acctcon: Cannot allocate memory\n");
    138 		exit(3);
    139 	}
    140 
    141 	/*
    142 	 * XXX - fixme - need a good way of getting the fd that getutxent would
    143 	 * use to access wtmpx, so we can convert this read of stdin to use
    144 	 * the APIs and remove the dependence on the existence of the file.
    145 	 */
    146 	while (fread(&wb, sizeof (wb), 1, stdin) == 1) {
    147 		if (firstime == 0)
    148 			firstime = wb.ut_xtime;
    149 		if (valid())
    150 			loop();
    151 		else
    152 			fixup(stderr);
    153 	}
    154 	wb.ut_name[0] = '\0';
    155 	strcpy(wb.ut_line, "acctcon");
    156 	wb.ut_type = ACCOUNTING;
    157 	wb.ut_xtime = lastime;
    158 	loop();
    159 
    160 	output();
    161 
    162 	if (report != NULL)
    163 		printrep();
    164 	if (replin != NULL)
    165 		printlin();
    166 
    167 	exit(exitcode);
    168 }
    169 
    170 
    171 /*
    172  * valid: check input wtmpx record, return 1 if looks OK
    173  */
    174 static int
    175 valid()
    176 {
    177 	int i, c;
    178 
    179 	/* XPG say that user names should not start with a "-" */
    180 	if ((c = wb.ut_name[0]) == '-')
    181 		return (0);
    182 
    183 	for (i = 0; i < NSZ; i++) {
    184 		c = wb.ut_name[i];
    185 		if (isalnum(c) || c == '$' || c == ' ' || c == '.' ||
    186 			c == '_' || c == '-')
    187 			continue;
    188 		else if (c == '\0')
    189 			break;
    190 		else
    191 			return (0);
    192 	}
    193 
    194 	if ((wb.ut_type >= EMPTY) && (wb.ut_type <= UTMAXTYPE))
    195 		return (1);
    196 
    197 	return (0);
    198 }
    199 
    200 static void
    201 fixup(FILE *stream)
    202 {
    203 	fprintf(stream, "bad wtmpx: offset %lu.\n", ftell(stdin)-sizeof (wb));
    204 	fprintf(stream, "bad record is:  %.*s\t%.*s\t%lu",
    205 	    sizeof (wb.ut_line),
    206 	    wb.ut_line,
    207 	    sizeof (wb.ut_name),
    208 	    wb.ut_name,
    209 	    wb.ut_xtime);
    210 	cftime(time_buf, DATE_FMT, &wb.ut_xtime);
    211 	fprintf(stream, "\t%s", time_buf);
    212 	exitcode = 1;
    213 }
    214 
    215 static void
    216 loop()
    217 {
    218 	int timediff;
    219 	struct tbuf *tp;
    220 
    221 	if (wb.ut_line[0] == '\0')	/* It's an init admin process */
    222 		return;			/* no connect accounting data here */
    223 	switch (wb.ut_type) {
    224 	case OLD_TIME:
    225 		datetime = wb.ut_xtime;
    226 		return;
    227 	case NEW_TIME:
    228 		if (datetime == 0)
    229 			return;
    230 		timediff = wb.ut_xtime - datetime;
    231 		for (tp = tbuf; tp <= &tbuf[tsize]; tp++)
    232 			tp->ttime += timediff;
    233 		datetime = 0;
    234 		ndates++;
    235 		return;
    236 	case DOWN_TIME:
    237 		return;
    238 	case BOOT_TIME:
    239 		upall();
    240 	case ACCOUNTING:
    241 	case RUN_LVL:
    242 		lastime = wb.ut_xtime;
    243 		bootshut();
    244 		return;
    245 	case USER_PROCESS:
    246 	case LOGIN_PROCESS:
    247 	case INIT_PROCESS:
    248 	case DEAD_PROCESS:	/* WHCC mod 3/86  */
    249 		update(&tbuf[iline()]);
    250 		return;
    251 	case EMPTY:
    252 		return;
    253 	default:
    254 		cftime(time_buf, DATE_FMT, &wb.ut_xtime);
    255 		fprintf(stderr, "acctcon: invalid type %d for %s %s %s",
    256 			wb.ut_type,
    257 			wb.ut_name,
    258 			wb.ut_line,
    259 			time_buf);
    260 	}
    261 }
    262 
    263 /*
    264  * bootshut: record reboot (or shutdown)
    265  * bump count, looking up wb.ut_line in sy table
    266  */
    267 static void
    268 bootshut()
    269 {
    270 	int i;
    271 
    272 	for (i = 0; i < nsys && !EQN(wb.ut_line, sy[i].sname); i++)
    273 		;
    274 	if (i >= nsys) {
    275 		if (++nsys > NSYS) {
    276 			fprintf(stderr,
    277 				"acctcon: recompile with larger NSYS\n");
    278 			nsys = NSYS;
    279 			return;
    280 		}
    281 		CPYN(sy[i].sname, wb.ut_line);
    282 	}
    283 	sy[i].snum++;
    284 }
    285 
    286 /*
    287  * iline: look up/enter current line name in tbuf, return index
    288  * (used to avoid system dependencies on naming)
    289  */
    290 static int
    291 iline()
    292 {
    293 	int i;
    294 
    295 	for (i = 0; i <= tsize; i++)
    296 		if (EQN(wb.ut_line, tbuf[i].tline))
    297 			return (i);
    298 	if (++tsize >= a_tsize) {
    299 		a_tsize = a_tsize + A_TSIZE;
    300 		if ((tbuf = (struct tbuf *)realloc(tbuf, a_tsize *
    301 			sizeof (struct tbuf))) == NULL) {
    302 			fprintf(stderr, "acctcon: Cannot reallocate memory\n");
    303 			exit(2);
    304 		}
    305 	}
    306 
    307 	CPYN(tbuf[tsize].tline, wb.ut_line);
    308 	tbuf[tsize].tdev = lintodev(wb.ut_line);
    309 	return (tsize);
    310 }
    311 
    312 static void
    313 upall()
    314 {
    315 	struct tbuf *tp;
    316 
    317 	wb.ut_type = DEAD_PROCESS;	/* fudge a logoff for reboot record. */
    318 	for (tp = tbuf; tp <= &tbuf[tsize]; tp++)
    319 		update(tp);
    320 }
    321 
    322 /*
    323  * update tbuf with new time, write ctmp record for end of session
    324  */
    325 static void
    326 update(struct tbuf *tp)
    327 {
    328 	time_t	told,	/* last time for tbuf record */
    329 		tnew;	/* time of this record */
    330 			/* Difference is connect time */
    331 
    332 	told = tp->ttime;
    333 	tnew = wb.ut_xtime;
    334 	if (told > tnew) {
    335 		cftime(time_buf, DATE_FMT, &told);
    336 		fprintf(stderr, "acctcon: bad times: old: %s", time_buf);
    337 		cftime(time_buf, DATE_FMT, &tnew);
    338 		fprintf(stderr, "new: %s", time_buf);
    339 		exitcode = 1;
    340 		tp->ttime = tnew;
    341 		return;
    342 	}
    343 	tp->ttime = tnew;
    344 	switch (wb.ut_type) {
    345 	case USER_PROCESS:
    346 		tp->tlsess++;
    347 		/*
    348 		 * Someone logged in without logging off. Put out record.
    349 		 */
    350 		if (tp->tname[0] != '\0') {
    351 			cb.ct_tty = tp->tdev;
    352 			CPYN(cb.ct_name, tp->tname);
    353 			cb.ct_uid = namtouid(cb.ct_name);
    354 			cb.ct_start = told;
    355 			if (pnpsplit(cb.ct_start, (ulong_t)(tnew-told),
    356 			    cb.ct_con) == 0) {
    357 				fprintf(stderr, "acctcon: could not calculate "
    358 				    "prime/non-prime hours\n");
    359 				exit(1);
    360 			}
    361 			enter(&cb);
    362 			tp->ttotal += tnew-told;
    363 		} else	/* Someone just logged in */
    364 			tp->tlon++;
    365 		CPYN(tp->tname, wb.ut_name);
    366 		break;
    367 	case DEAD_PROCESS:
    368 		tp->tloff++;
    369 		if (tp->tname[0] != '\0') { /* Someone logged off */
    370 			/* Set up and print ctmp record */
    371 			cb.ct_tty = tp->tdev;
    372 			CPYN(cb.ct_name, tp->tname);
    373 			cb.ct_uid = namtouid(cb.ct_name);
    374 			cb.ct_start = told;
    375 			if (pnpsplit(cb.ct_start, (ulong_t)(tnew-told),
    376 			    cb.ct_con) == 0) {
    377 				fprintf(stderr, "acctcon: could not calculate "
    378 				    "prime/non-prime hours\n");
    379 				exit(1);
    380 			}
    381 			enter(&cb);
    382 			tp->ttotal += tnew-told;
    383 			tp->tname[0] = '\0';
    384 		}
    385 	}
    386 }
    387 
    388 static void
    389 printrep()
    390 {
    391 	int i;
    392 
    393 	freopen(report, "w", stdout);
    394 	cftime(time_buf, DATE_FMT, &firstime);
    395 	printf("from %s", time_buf);
    396 	cftime(time_buf, DATE_FMT, &lastime);
    397 	printf("to   %s", time_buf);
    398 	if (ndates)
    399 		printf("%d\tdate change%c\n", ndates, (ndates > 1 ? 's' :
    400 		    '\0'));
    401 	for (i = 0; i < nsys; i++)
    402 		printf("%d\t%.*s\n", sy[i].snum,
    403 		    sizeof (sy[i].sname), sy[i].sname);
    404 }
    405 
    406 
    407 /*
    408  *	print summary of line usage
    409  *	accuracy only guaranteed for wtmpx file started fresh
    410  */
    411 static void
    412 printlin()
    413 {
    414 	struct tbuf *tp;
    415 	double ttime;
    416 	int tsess, ton, toff;
    417 
    418 	freopen(replin, "w", stdout);
    419 	ttime = 0.0;
    420 	tsess = ton = toff = 0;
    421 	timet = MINS(lastime-firstime);
    422 	printf("TOTAL DURATION IS %.0f MINUTES\n", timet);
    423 	printf("LINE         MINUTES  PERCENT  # SESS  # ON  # OFF\n");
    424 	qsort((char *)tbuf, tsize + 1, sizeof (tbuf[0]),
    425 	    (int (*)(const void *, const void *))tcmp);
    426 	for (tp = tbuf; tp <= &tbuf[tsize]; tp++) {
    427 		timei = MINS(tp->ttotal);
    428 		ttime += timei;
    429 		tsess += tp->tlsess;
    430 		ton += tp->tlon;
    431 		toff += tp->tloff;
    432 		printf("%-*.*s %-7.0f  %-7.0f  %-6d  %-4d  %-5d\n",
    433 		    OUTPUT_LSZ,
    434 		    OUTPUT_LSZ,
    435 		    tp->tline,
    436 		    timei,
    437 		    (timet > 0.)? 100*timei/timet : 0.,
    438 		    tp->tlsess,
    439 		    tp->tlon,
    440 		    tp->tloff);
    441 	}
    442 	printf("TOTALS       %-7.0f  --       %-6d  %-4d  %-5d\n",
    443 	    ttime, tsess, ton, toff);
    444 }
    445 
    446 static int
    447 tcmp(struct tbuf *t1, struct tbuf *t2)
    448 {
    449 	return (strncmp(t1->tline, t2->tline, LSZ));
    450 }
    451 
    452 static int
    453 node_compare(const void *node1, const void *node2)
    454 {
    455 	if (((const struct ctab *)node1)->ct_uid >
    456 	    ((const struct ctab *)node2)->ct_uid)
    457 		return (1);
    458 	else if (((const struct ctab *)node1)->ct_uid <
    459 	    ((const struct ctab *)node2)->ct_uid)
    460 		return (-1);
    461 	else
    462 		return (0);
    463 }
    464 
    465 static void
    466 enter(struct ctmp *c)
    467 {
    468 	unsigned i;
    469 	int j;
    470 	struct ctab **pt;
    471 
    472 	if ((pctab = (struct ctab *)malloc(sizeof (struct ctab))) == NULL) {
    473 		fprintf(stderr, "acctcon: malloc fail!\n");
    474 		exit(2);
    475 	}
    476 
    477 	pctab->ct_uid = c->ct_uid;
    478 	CPYN(pctab->ct_name, c->ct_name);
    479 	pctab->ct_con[0] = c->ct_con[0];
    480 	pctab->ct_con[1] = c->ct_con[1];
    481 	pctab->ct_sess = 1;
    482 
    483 	if (*(pt = (struct ctab **)tsearch((void *)pctab, (void **)&root,  \
    484 		node_compare)) == NULL) {
    485 		fprintf(stderr, "Not enough space available to build tree\n");
    486 		exit(1);
    487 	}
    488 
    489 	if (*pt != pctab) {
    490 		(*pt)->ct_con[0] += c->ct_con[0];
    491 		(*pt)->ct_con[1] += c->ct_con[1];
    492 		(*pt)->ct_sess++;
    493 		free(pctab);
    494 	}
    495 
    496 }
    497 
    498 static void
    499 print_node(const void *node, VISIT order, int level)
    500 {
    501 	if (order == postorder || order == leaf) {
    502 		tb.ta_uid = (*(struct ctab **)node)->ct_uid;
    503 		CPYN(tb.ta_name, (*(struct ctab **)node)->ct_name);
    504 		tb.ta_con[0] = ((*(struct ctab **)node)->ct_con[0]) / 60.0;
    505 		tb.ta_con[1] = ((*(struct ctab **)node)->ct_con[1]) / 60.0;
    506 		tb.ta_sc = (*(struct ctab **)node)->ct_sess;
    507 		fwrite(&tb, sizeof (tb), 1, stdout);
    508 	}
    509 }
    510 
    511 static void
    512 output()
    513 {
    514 	twalk((struct ctab *)root, print_node);
    515 }
    516