OpenSolaris_b135/cmd/acct/acctcon.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/


/*
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */
#pragma ident	"%Z%%M%	%I%	%E% SMI"


/*
 *	acctcon [-l file] [-o file] <wtmpx-file
 *	-l file	causes output of line usage summary
 *	-o file	causes first/last/reboots report to be written to file
 *	reads input (normally /var/adm/wtmpx), produces
 *	list of sessions, sorted by ending time in tacct.h format
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/param.h>
#include "acctdef.h"
#include <ctype.h>
#include <time.h>
#include <utmpx.h>
#include <locale.h>
#include <string.h>
#include <search.h>
#include <stdlib.h>

int   a_tsize = A_TSIZE;
int	tsize	= -1;	/* highest index of used slot in tbuf table */
static	int csize;
struct  utmpx	wb;	/* record structure read into */
struct	ctmp	cb;	/* record structure written out of */
struct	tacct	tb;
double	timet, timei;

struct tbuf {
	char	tline[LSZ];	/* /dev/...  */
	char	tname[NSZ];	/* user name */
	time_t	ttime;		/* start time */
	dev_t	tdev;		/* device */
	int	tlsess;		/* # complete sessions */
	int	tlon;		/* # times on (ut_type of 7) */
	int	tloff;		/* # times off (ut_type != 7) */
	long	ttotal;		/* total time used on this line */
} *tbuf;

struct ctab {
	uid_t		ct_uid;
	char		ct_name[NSZ];
	long 		ct_con[2];
	ushort_t	ct_sess;
} *pctab;

int	nsys;
struct sys {
	char	sname[LSZ];	/* reasons for ACCOUNTING records */
	char	snum;		/* number of times encountered */
} sy[NSYS];

static char time_buf[50];
time_t	datetime;	/* old time if date changed, otherwise 0 */
time_t	firstime;
time_t	lastime;
int	ndates;		/* number of times date changed */
int	exitcode;
char	*report	= NULL;
char	*replin = NULL;

uid_t	namtouid();
dev_t	lintodev();
static int valid(void);
static void fixup(FILE *);
static void loop(void);
static void bootshut(void);
static int iline(void);
static void upall(void);
static void update(struct tbuf *);
static void printrep(void);
static void printlin(void);
static int tcmp(struct tbuf *, struct tbuf *);
static int node_compare(const void *, const void *);
static void enter(struct ctmp *);
static void print_node(const void *, VISIT, int);
static void output(void);

extern char 	*optarg;
extern int	optind;

void **root = NULL;

int
main(int argc, char **argv)
{
	int c;

	(void) setlocale(LC_ALL, "");
	while ((c = getopt(argc, argv, "l:o:")) != EOF)
		switch (c) {
		case 'l':
			replin = optarg;
			break;
		case 'o':
			report = optarg;
			break;
		case '?':
			fprintf(stderr, "usage: %s [-l lineuse] "
			    "[-o reboot]\n", argv[0]);
			exit(1);
		}

	if ((tbuf = (struct tbuf *)calloc(a_tsize,
		sizeof (struct tbuf))) == NULL) {
		fprintf(stderr, "acctcon: Cannot allocate memory\n");
		exit(3);
	}

	/*
	 * XXX - fixme - need a good way of getting the fd that getutxent would
	 * use to access wtmpx, so we can convert this read of stdin to use
	 * the APIs and remove the dependence on the existence of the file.
	 */
	while (fread(&wb, sizeof (wb), 1, stdin) == 1) {
		if (firstime == 0)
			firstime = wb.ut_xtime;
		if (valid())
			loop();
		else
			fixup(stderr);
	}
	wb.ut_name[0] = '\0';
	strcpy(wb.ut_line, "acctcon");
	wb.ut_type = ACCOUNTING;
	wb.ut_xtime = lastime;
	loop();

	output();

	if (report != NULL)
		printrep();
	if (replin != NULL)
		printlin();

	exit(exitcode);
}


/*
 * valid: check input wtmpx record, return 1 if looks OK
 */
static int
valid()
{
	int i, c;

	/* XPG say that user names should not start with a "-" */
	if ((c = wb.ut_name[0]) == '-')
		return (0);

	for (i = 0; i < NSZ; i++) {
		c = wb.ut_name[i];
		if (isalnum(c) || c == '$' || c == ' ' || c == '.' ||
			c == '_' || c == '-')
			continue;
		else if (c == '\0')
			break;
		else
			return (0);
	}

	if ((wb.ut_type >= EMPTY) && (wb.ut_type <= UTMAXTYPE))
		return (1);

	return (0);
}

static void
fixup(FILE *stream)
{
	fprintf(stream, "bad wtmpx: offset %lu.\n", ftell(stdin)-sizeof (wb));
	fprintf(stream, "bad record is:  %.*s\t%.*s\t%lu",
	    sizeof (wb.ut_line),
	    wb.ut_line,
	    sizeof (wb.ut_name),
	    wb.ut_name,
	    wb.ut_xtime);
	cftime(time_buf, DATE_FMT, &wb.ut_xtime);
	fprintf(stream, "\t%s", time_buf);
	exitcode = 1;
}

static void
loop()
{
	int timediff;
	struct tbuf *tp;

	if (wb.ut_line[0] == '\0')	/* It's an init admin process */
		return;			/* no connect accounting data here */
	switch (wb.ut_type) {
	case OLD_TIME:
		datetime = wb.ut_xtime;
		return;
	case NEW_TIME:
		if (datetime == 0)
			return;
		timediff = wb.ut_xtime - datetime;
		for (tp = tbuf; tp <= &tbuf[tsize]; tp++)
			tp->ttime += timediff;
		datetime = 0;
		ndates++;
		return;
	case DOWN_TIME:
		return;
	case BOOT_TIME:
		upall();
	case ACCOUNTING:
	case RUN_LVL:
		lastime = wb.ut_xtime;
		bootshut();
		return;
	case USER_PROCESS:
	case LOGIN_PROCESS:
	case INIT_PROCESS:
	case DEAD_PROCESS:	/* WHCC mod 3/86  */
		update(&tbuf[iline()]);
		return;
	case EMPTY:
		return;
	default:
		cftime(time_buf, DATE_FMT, &wb.ut_xtime);
		fprintf(stderr, "acctcon: invalid type %d for %s %s %s",
			wb.ut_type,
			wb.ut_name,
			wb.ut_line,
			time_buf);
	}
}

/*
 * bootshut: record reboot (or shutdown)
 * bump count, looking up wb.ut_line in sy table
 */
static void
bootshut()
{
	int i;

	for (i = 0; i < nsys && !EQN(wb.ut_line, sy[i].sname); i++)
		;
	if (i >= nsys) {
		if (++nsys > NSYS) {
			fprintf(stderr,
				"acctcon: recompile with larger NSYS\n");
			nsys = NSYS;
			return;
		}
		CPYN(sy[i].sname, wb.ut_line);
	}
	sy[i].snum++;
}

/*
 * iline: look up/enter current line name in tbuf, return index
 * (used to avoid system dependencies on naming)
 */
static int
iline()
{
	int i;

	for (i = 0; i <= tsize; i++)
		if (EQN(wb.ut_line, tbuf[i].tline))
			return (i);
	if (++tsize >= a_tsize) {
		a_tsize = a_tsize + A_TSIZE;
		if ((tbuf = (struct tbuf *)realloc(tbuf, a_tsize *
			sizeof (struct tbuf))) == NULL) {
			fprintf(stderr, "acctcon: Cannot reallocate memory\n");
			exit(2);
		}
	}

	CPYN(tbuf[tsize].tline, wb.ut_line);
	tbuf[tsize].tdev = lintodev(wb.ut_line);
	return (tsize);
}

static void
upall()
{
	struct tbuf *tp;

	wb.ut_type = DEAD_PROCESS;	/* fudge a logoff for reboot record. */
	for (tp = tbuf; tp <= &tbuf[tsize]; tp++)
		update(tp);
}

/*
 * update tbuf with new time, write ctmp record for end of session
 */
static void
update(struct tbuf *tp)
{
	time_t	told,	/* last time for tbuf record */
		tnew;	/* time of this record */
			/* Difference is connect time */

	told = tp->ttime;
	tnew = wb.ut_xtime;
	if (told > tnew) {
		cftime(time_buf, DATE_FMT, &told);
		fprintf(stderr, "acctcon: bad times: old: %s", time_buf);
		cftime(time_buf, DATE_FMT, &tnew);
		fprintf(stderr, "new: %s", time_buf);
		exitcode = 1;
		tp->ttime = tnew;
		return;
	}
	tp->ttime = tnew;
	switch (wb.ut_type) {
	case USER_PROCESS:
		tp->tlsess++;
		/*
		 * Someone logged in without logging off. Put out record.
		 */
		if (tp->tname[0] != '\0') {
			cb.ct_tty = tp->tdev;
			CPYN(cb.ct_name, tp->tname);
			cb.ct_uid = namtouid(cb.ct_name);
			cb.ct_start = told;
			if (pnpsplit(cb.ct_start, (ulong_t)(tnew-told),
			    cb.ct_con) == 0) {
				fprintf(stderr, "acctcon: could not calculate "
				    "prime/non-prime hours\n");
				exit(1);
			}
			enter(&cb);
			tp->ttotal += tnew-told;
		} else	/* Someone just logged in */
			tp->tlon++;
		CPYN(tp->tname, wb.ut_name);
		break;
	case DEAD_PROCESS:
		tp->tloff++;
		if (tp->tname[0] != '\0') { /* Someone logged off */
			/* Set up and print ctmp record */
			cb.ct_tty = tp->tdev;
			CPYN(cb.ct_name, tp->tname);
			cb.ct_uid = namtouid(cb.ct_name);
			cb.ct_start = told;
			if (pnpsplit(cb.ct_start, (ulong_t)(tnew-told),
			    cb.ct_con) == 0) {
				fprintf(stderr, "acctcon: could not calculate "
				    "prime/non-prime hours\n");
				exit(1);
			}
			enter(&cb);
			tp->ttotal += tnew-told;
			tp->tname[0] = '\0';
		}
	}
}

static void
printrep()
{
	int i;

	freopen(report, "w", stdout);
	cftime(time_buf, DATE_FMT, &firstime);
	printf("from %s", time_buf);
	cftime(time_buf, DATE_FMT, &lastime);
	printf("to   %s", time_buf);
	if (ndates)
		printf("%d\tdate change%c\n", ndates, (ndates > 1 ? 's' :
		    '\0'));
	for (i = 0; i < nsys; i++)
		printf("%d\t%.*s\n", sy[i].snum,
		    sizeof (sy[i].sname), sy[i].sname);
}


/*
 *	print summary of line usage
 *	accuracy only guaranteed for wtmpx file started fresh
 */
static void
printlin()
{
	struct tbuf *tp;
	double ttime;
	int tsess, ton, toff;

	freopen(replin, "w", stdout);
	ttime = 0.0;
	tsess = ton = toff = 0;
	timet = MINS(lastime-firstime);
	printf("TOTAL DURATION IS %.0f MINUTES\n", timet);
	printf("LINE         MINUTES  PERCENT  # SESS  # ON  # OFF\n");
	qsort((char *)tbuf, tsize + 1, sizeof (tbuf[0]),
	    (int (*)(const void *, const void *))tcmp);
	for (tp = tbuf; tp <= &tbuf[tsize]; tp++) {
		timei = MINS(tp->ttotal);
		ttime += timei;
		tsess += tp->tlsess;
		ton += tp->tlon;
		toff += tp->tloff;
		printf("%-*.*s %-7.0f  %-7.0f  %-6d  %-4d  %-5d\n",
		    OUTPUT_LSZ,
		    OUTPUT_LSZ,
		    tp->tline,
		    timei,
		    (timet > 0.)? 100*timei/timet : 0.,
		    tp->tlsess,
		    tp->tlon,
		    tp->tloff);
	}
	printf("TOTALS       %-7.0f  --       %-6d  %-4d  %-5d\n",
	    ttime, tsess, ton, toff);
}

static int
tcmp(struct tbuf *t1, struct tbuf *t2)
{
	return (strncmp(t1->tline, t2->tline, LSZ));
}

static int
node_compare(const void *node1, const void *node2)
{
	if (((const struct ctab *)node1)->ct_uid >
	    ((const struct ctab *)node2)->ct_uid)
		return (1);
	else if (((const struct ctab *)node1)->ct_uid <
	    ((const struct ctab *)node2)->ct_uid)
		return (-1);
	else
		return (0);
}

static void
enter(struct ctmp *c)
{
	unsigned i;
	int j;
	struct ctab **pt;

	if ((pctab = (struct ctab *)malloc(sizeof (struct ctab))) == NULL) {
		fprintf(stderr, "acctcon: malloc fail!\n");
		exit(2);
	}

	pctab->ct_uid = c->ct_uid;
	CPYN(pctab->ct_name, c->ct_name);
	pctab->ct_con[0] = c->ct_con[0];
	pctab->ct_con[1] = c->ct_con[1];
	pctab->ct_sess = 1;

	if (*(pt = (struct ctab **)tsearch((void *)pctab, (void **)&root,  \
		node_compare)) == NULL) {
		fprintf(stderr, "Not enough space available to build tree\n");
		exit(1);
	}

	if (*pt != pctab) {
		(*pt)->ct_con[0] += c->ct_con[0];
		(*pt)->ct_con[1] += c->ct_con[1];
		(*pt)->ct_sess++;
		free(pctab);
	}

}

static void
print_node(const void *node, VISIT order, int level)
{
	if (order == postorder || order == leaf) {
		tb.ta_uid = (*(struct ctab **)node)->ct_uid;
		CPYN(tb.ta_name, (*(struct ctab **)node)->ct_name);
		tb.ta_con[0] = ((*(struct ctab **)node)->ct_con[0]) / 60.0;
		tb.ta_con[1] = ((*(struct ctab **)node)->ct_con[1]) / 60.0;
		tb.ta_sc = (*(struct ctab **)node)->ct_sess;
		fwrite(&tb, sizeof (tb), 1, stdout);
	}
}

static void
output()
{
	twalk((struct ctab *)root, print_node);
}