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
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		/* FALLTHROUGH */
263	case ACCOUNTING:
264	case RUN_LVL:
265		lastime = wb.ut_xtime;
266		bootshut();
267		return;
268	case USER_PROCESS:
269	case LOGIN_PROCESS:
270	case INIT_PROCESS:
271	case DEAD_PROCESS:
272		update(&tbuf[iline()]);
273		return;
274	case EMPTY:
275		return;
276	default:
277		cftime(time_buf, DATE_FMT, &wb.ut_xtime);
278		fprintf(stderr, "acctcon1: invalid type %d for %s %s %s",
279		    wb.ut_type,
280		    wb.ut_name,
281		    wb.ut_line,
282		    time_buf);
283	}
284}
285
286/*
287 * bootshut: record reboot (or shutdown)
288 * bump count, looking up wb.ut_line in sy table
289 */
290static void
291bootshut()
292{
293	int i;
294
295	for (i = 0; i < nsys && !EQN(wb.ut_line, sy[i].sname); i++)
296		;
297	if (i >= nsys) {
298		if (++nsys > NSYS) {
299			fprintf(stderr,
300				"acctcon1: recompile with larger NSYS\n");
301			nsys = NSYS;
302			return;
303		}
304		CPYN(sy[i].sname, wb.ut_line);
305	}
306	sy[i].snum++;
307}
308
309/*
310 * iline: look up/enter current line name in tbuf, return index
311 * (used to avoid system dependencies on naming)
312 */
313static int
314iline()
315{
316	int i;
317
318	for (i = 0; i <= tsize; i++)
319		if (EQN(wb.ut_line, tbuf[i].tline))
320			return(i);
321	if (++tsize >= a_tsize) {
322		a_tsize = a_tsize + A_TSIZE;
323		if ((tbuf = (struct tbuf *) realloc(tbuf, a_tsize *
324			sizeof (struct tbuf))) == NULL) {
325			fprintf(stderr, "acctcon1: Cannot reallocate memory\n");
326			exit(2);
327		}
328	}
329
330	CPYN(tbuf[tsize].tline, wb.ut_line);
331	tbuf[tsize].tdev = lintodev(wb.ut_line);
332	return(tsize);
333}
334
335static void
336upall()
337{
338	struct tbuf *tp;
339
340	wb.ut_type = INIT_PROCESS;	/* fudge a logoff for reboot record */
341	for (tp = tbuf; tp <= &tbuf[tsize]; tp++)
342		update(tp);
343}
344
345/*
346 * update tbuf with new time, write ctmp record for end of session
347 */
348static void
349update(struct tbuf *tp)
350{
351	time_t	told,	/* last time for tbuf record */
352		tnew;	/* time of this record */
353			/* Difference is connect time */
354
355	told = tp->ttime;
356	tnew = wb.ut_xtime;
357	cftime(time_buf, DATE_FMT, &told);
358	fprintf(stderr, "The old time is: %s", time_buf);
359	cftime(time_buf, DATE_FMT, &tnew);
360	fprintf(stderr, "the new time is: %s", time_buf);
361	if (told > tnew) {
362		cftime(time_buf, DATE_FMT, &told);
363		fprintf(stderr, "acctcon1: bad times: old: %s", time_buf);
364		cftime(time_buf, DATE_FMT, &tnew);
365		fprintf(stderr, "new: %s", time_buf);
366		exitcode = 1;
367		tp->ttime = tnew;
368		return;
369	}
370	tp->ttime = tnew;
371	switch(wb.ut_type) {
372	case USER_PROCESS:
373		tp->tlsess++;
374		if(tp->tname[0] != '\0') { /* Someone logged in without */
375					   /* logging off. Put out record. */
376			cb.ct_tty = tp->tdev;
377			CPYN(cb.ct_name, tp->tname);
378			cb.ct_uid = namtouid(cb.ct_name);
379			cb.ct_start = told;
380			if (pnpsplit(cb.ct_start, (ulong_t)(tnew-told),
381			    cb.ct_con) == 0) {
382				fprintf(stderr, "acctcon1: could not calculate prime/non-prime hours\n");
383
384				exit(1);
385			}
386			prctmp(&cb);
387			tp->ttotal += tnew-told;
388		}
389		else	/* Someone just logged in */
390			tp->tlon++;
391		CPYN(tp->tname, wb.ut_name);
392		break;
393	case INIT_PROCESS:
394	case LOGIN_PROCESS:
395	case DEAD_PROCESS:
396		tp->tloff++;
397		if(tp->tname[0] != '\0') { /* Someone logged off */
398			/* Set up and print ctmp record */
399			cb.ct_tty = tp->tdev;
400			CPYN(cb.ct_name, tp->tname);
401			cb.ct_uid = namtouid(cb.ct_name);
402			cb.ct_start = told;
403			if (pnpsplit(cb.ct_start, (ulong_t)(tnew-told),
404			    cb.ct_con) == 0) {
405				fprintf(stderr, "acctcon1: could not calculate prime/non-prime hours\n");
406				exit(1);
407			}
408			prctmp(&cb);
409			tp->ttotal += tnew-told;
410			tp->tname[0] = '\0';
411		}
412	}
413}
414
415static void
416printrep()
417{
418	int i;
419
420	freopen(report, "w", stdout);
421	cftime(time_buf, DATE_FMT, &firstime);
422	printf("from %s", time_buf);
423	cftime(time_buf, DATE_FMT, &lastime);
424	printf("to   %s", time_buf);
425	if (ndates)
426		printf("%d\tdate change%c\n",ndates,(ndates>1 ? 's' : '\0'));
427	for (i = 0; i < nsys; i++)
428		printf("%d\t%.*s\n", sy[i].snum,
429		    sizeof (sy[i].sname), sy[i].sname);
430}
431
432/*
433 *	print summary of line usage
434 *	accuracy only guaranteed for wtmpx file started fresh
435 */
436static void
437printlin()
438{
439	struct tbuf *tp;
440	double timet, timei;
441	double ttime;
442	int tsess, ton, toff;
443
444	freopen(replin, "w", stdout);
445	ttime = 0.0;
446	tsess = ton = toff = 0;
447	timet = MINS(lastime-firstime);
448	printf("TOTAL DURATION IS %.0f MINUTES\n", timet);
449	printf("LINE         MINUTES  PERCENT  # SESS  # ON  # OFF\n");
450	for (tp = tbuf; tp <= &tbuf[tsize]; tp++) {
451		timei = MINS(tp->ttotal);
452		ttime += timei;
453		tsess += tp->tlsess;
454		ton += tp->tlon;
455		toff += tp->tloff;
456		printf("%-*.*s %-7.0f  %-7.0f  %-6d  %-4d  %-5d\n",
457		    OUTPUT_LSZ,
458		    OUTPUT_LSZ,
459		    tp->tline,
460		    timei,
461		    (timet > 0.)? 100*timei/timet : 0.,
462		    tp->tlsess,
463		    tp->tlon,
464		    tp->tloff);
465	}
466	printf("TOTALS       %-7.0f  --       %-6d  %-4d  %-5d\n",
467	    ttime, tsess, ton, toff);
468}
469
470static void
471prctmp(struct ctmp *t)
472{
473
474	printf("%u\t%ld\t%.*s\t%lu\t%lu\t%lu",
475	    t->ct_tty,
476	    t->ct_uid,
477	    OUTPUT_NSZ,
478	    t->ct_name,
479	    t->ct_con[0],
480	    t->ct_con[1],
481	    t->ct_start);
482	cftime(time_buf, DATE_FMT, &t->ct_start);
483	printf("\t%s", time_buf);
484}
485