xref: /illumos-gate/usr/src/cmd/acct/acctcon1.c (revision 8cd45542f2a452ca0dab13d8b2d5cfa876ccbebc)
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 
53 int	a_tsize	= A_TSIZE;
54 int	tsize	= -1;	/* used slots in tbuf table */
55 struct  utmpx	wb;	/* record structure read into */
56 struct	ctmp	cb;	/* record structure written out of */
57 
58 struct 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"
70 int	nsys;
71 struct sys {
72 	char	sname[LSZ];	/* reasons for ACCOUNTING records */
73 	char	snum;		/* number of times encountered */
74 } sy[NSYS];
75 
76 time_t	datetime;	/* old time if date changed, otherwise 0 */
77 time_t	firstime;
78 time_t	lastime;
79 int	ndates;		/* number of times date changed */
80 int	exitcode;
81 char	*report	= NULL;
82 char	*replin = NULL;
83 int	printonly;
84 int	tflag;
85 
86 static char time_buf[50];
87 uid_t	namtouid();
88 dev_t	lintodev();
89 static size_t wread(void);
90 static int valid(void);
91 static void fixup(FILE *);
92 static void loop(void);
93 static void bootshut(void);
94 static int iline(void);
95 static void upall(void);
96 static void update(struct tbuf *);
97 static void printrep(void);
98 static void printlin(void);
99 static void prctmp(struct ctmp *);
100 
101 int
102 main(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 
176 static size_t
177 wread()
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  */
186 static int
187 valid()
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  */
221 static void
222 fixup(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 
239 static void
240 loop()
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  */
289 static void
290 bootshut()
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  */
312 static int
313 iline()
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 
334 static void
335 upall()
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  */
347 static void
348 update(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 
414 static void
415 printrep()
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  */
435 static void
436 printlin()
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 
469 static void
470 prctmp(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