xref: /illumos-gate/usr/src/cmd/acct/acctcon1.c (revision 55fea89d)
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 
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
main(int argc,char ** argv)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
wread()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
valid()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
fixup(FILE * stream)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
loop()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 		/* 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  */
290 static void
bootshut()291 bootshut()
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  */
313 static int
iline()314 iline()
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 
335 static void
upall()336 upall()
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  */
348 static void
update(struct tbuf * tp)349 update(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 
415 static void
printrep()416 printrep()
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  */
436 static void
printlin()437 printlin()
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 
470 static void
prctmp(struct ctmp * t)471 prctmp(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