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