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
52int   a_tsize = A_TSIZE;
53int	tsize	= -1;	/* highest index of used slot in tbuf table */
54static	int csize;
55struct  utmpx	wb;	/* record structure read into */
56struct	ctmp	cb;	/* record structure written out of */
57struct	tacct	tb;
58double	timet, timei;
59
60struct 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
71struct ctab {
72	uid_t		ct_uid;
73	char		ct_name[NSZ];
74	long 		ct_con[2];
75	ushort_t	ct_sess;
76} *pctab;
77
78int	nsys;
79struct sys {
80	char	sname[LSZ];	/* reasons for ACCOUNTING records */
81	char	snum;		/* number of times encountered */
82} sy[NSYS];
83
84static char time_buf[50];
85time_t	datetime;	/* old time if date changed, otherwise 0 */
86time_t	firstime;
87time_t	lastime;
88int	ndates;		/* number of times date changed */
89int	exitcode;
90char	*report	= NULL;
91char	*replin = NULL;
92
93uid_t	namtouid();
94dev_t	lintodev();
95static int valid(void);
96static void fixup(FILE *);
97static void loop(void);
98static void bootshut(void);
99static int iline(void);
100static void upall(void);
101static void update(struct tbuf *);
102static void printrep(void);
103static void printlin(void);
104static int tcmp(struct tbuf *, struct tbuf *);
105static int node_compare(const void *, const void *);
106static void enter(struct ctmp *);
107static void print_node(const void *, VISIT, int);
108static void output(void);
109
110extern char 	*optarg;
111extern int	optind;
112
113void **root = NULL;
114
115int
116main(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 */
174static int
175valid()
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
200static void
201fixup(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
215static void
216loop()
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 */
267static void
268bootshut()
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 */
290static int
291iline()
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
312static void
313upall()
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 */
325static void
326update(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
388static void
389printrep()
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 */
411static void
412printlin()
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
446static int
447tcmp(struct tbuf *t1, struct tbuf *t2)
448{
449	return (strncmp(t1->tline, t2->tline, LSZ));
450}
451
452static int
453node_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
465static void
466enter(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
498static void
499print_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
511static void
512output()
513{
514	twalk((struct ctab *)root, print_node);
515}
516