xref: /illumos-gate/usr/src/cmd/acct/acctcon.c (revision 49218d4f8e4d84d1c08aeb267bcf6e451f2056dc)
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 
33 /*
34  *	acctcon [-l file] [-o file] <wtmpx-file
35  *	-l file	causes output of line usage summary
36  *	-o file	causes first/last/reboots report to be written to file
37  *	reads input (normally /var/adm/wtmpx), produces
38  *	list of sessions, sorted by ending time in tacct.h format
39  */
40 
41 #include <stdio.h>
42 #include <sys/types.h>
43 #include <sys/param.h>
44 #include "acctdef.h"
45 #include <ctype.h>
46 #include <time.h>
47 #include <utmpx.h>
48 #include <locale.h>
49 #include <string.h>
50 #include <search.h>
51 #include <stdlib.h>
52 
53 int   a_tsize = A_TSIZE;
54 int	tsize	= -1;	/* highest index of used slot in tbuf table */
55 static	int csize;
56 struct  utmpx	wb;	/* record structure read into */
57 struct	ctmp	cb;	/* record structure written out of */
58 struct	tacct	tb;
59 double	timet, timei;
60 
61 struct tbuf {
62 	char	tline[LSZ];	/* /dev/...  */
63 	char	tname[NSZ];	/* user name */
64 	time_t	ttime;		/* start time */
65 	dev_t	tdev;		/* device */
66 	int	tlsess;		/* # complete sessions */
67 	int	tlon;		/* # times on (ut_type of 7) */
68 	int	tloff;		/* # times off (ut_type != 7) */
69 	long	ttotal;		/* total time used on this line */
70 } *tbuf;
71 
72 struct ctab {
73 	uid_t		ct_uid;
74 	char		ct_name[NSZ];
75 	long 		ct_con[2];
76 	ushort_t	ct_sess;
77 } *pctab;
78 
79 int	nsys;
80 struct sys {
81 	char	sname[LSZ];	/* reasons for ACCOUNTING records */
82 	char	snum;		/* number of times encountered */
83 } sy[NSYS];
84 
85 static char time_buf[50];
86 time_t	datetime;	/* old time if date changed, otherwise 0 */
87 time_t	firstime;
88 time_t	lastime;
89 int	ndates;		/* number of times date changed */
90 int	exitcode;
91 char	*report	= NULL;
92 char	*replin = NULL;
93 
94 uid_t	namtouid();
95 dev_t	lintodev();
96 static int valid(void);
97 static void fixup(FILE *);
98 static void loop(void);
99 static void bootshut(void);
100 static int iline(void);
101 static void upall(void);
102 static void update(struct tbuf *);
103 static void printrep(void);
104 static void printlin(void);
105 static int tcmp(struct tbuf *, struct tbuf *);
106 static int node_compare(const void *, const void *);
107 static void enter(struct ctmp *);
108 static void print_node(const void *, VISIT, int);
109 static void output(void);
110 
111 extern char 	*optarg;
112 extern int	optind;
113 
114 void **root = NULL;
115 
116 int
117 main(int argc, char **argv)
118 {
119 	int c;
120 
121 	(void) setlocale(LC_ALL, "");
122 	while ((c = getopt(argc, argv, "l:o:")) != EOF)
123 		switch (c) {
124 		case 'l':
125 			replin = optarg;
126 			break;
127 		case 'o':
128 			report = optarg;
129 			break;
130 		case '?':
131 			fprintf(stderr, "usage: %s [-l lineuse] "
132 			    "[-o reboot]\n", argv[0]);
133 			exit(1);
134 		}
135 
136 	if ((tbuf = (struct tbuf *)calloc(a_tsize,
137 		sizeof (struct tbuf))) == NULL) {
138 		fprintf(stderr, "acctcon: Cannot allocate memory\n");
139 		exit(3);
140 	}
141 
142 	/*
143 	 * XXX - fixme - need a good way of getting the fd that getutxent would
144 	 * use to access wtmpx, so we can convert this read of stdin to use
145 	 * the APIs and remove the dependence on the existence of the file.
146 	 */
147 	while (fread(&wb, sizeof (wb), 1, stdin) == 1) {
148 		if (firstime == 0)
149 			firstime = wb.ut_xtime;
150 		if (valid())
151 			loop();
152 		else
153 			fixup(stderr);
154 	}
155 	wb.ut_name[0] = '\0';
156 	strcpy(wb.ut_line, "acctcon");
157 	wb.ut_type = ACCOUNTING;
158 	wb.ut_xtime = lastime;
159 	loop();
160 
161 	output();
162 
163 	if (report != NULL)
164 		printrep();
165 	if (replin != NULL)
166 		printlin();
167 
168 	exit(exitcode);
169 }
170 
171 
172 /*
173  * valid: check input wtmpx record, return 1 if looks OK
174  */
175 static int
176 valid()
177 {
178 	int i, c;
179 
180 	/* XPG say that user names should not start with a "-" */
181 	if ((c = wb.ut_name[0]) == '-')
182 		return (0);
183 
184 	for (i = 0; i < NSZ; i++) {
185 		c = wb.ut_name[i];
186 		if (isalnum(c) || c == '$' || c == ' ' || c == '.' ||
187 			c == '_' || c == '-')
188 			continue;
189 		else if (c == '\0')
190 			break;
191 		else
192 			return (0);
193 	}
194 
195 	if ((wb.ut_type >= EMPTY) && (wb.ut_type <= UTMAXTYPE))
196 		return (1);
197 
198 	return (0);
199 }
200 
201 static void
202 fixup(FILE *stream)
203 {
204 	fprintf(stream, "bad wtmpx: offset %lu.\n", ftell(stdin)-sizeof (wb));
205 	fprintf(stream, "bad record is:  %.*s\t%.*s\t%lu",
206 	    sizeof (wb.ut_line),
207 	    wb.ut_line,
208 	    sizeof (wb.ut_name),
209 	    wb.ut_name,
210 	    wb.ut_xtime);
211 	cftime(time_buf, DATE_FMT, &wb.ut_xtime);
212 	fprintf(stream, "\t%s", time_buf);
213 	exitcode = 1;
214 }
215 
216 static void
217 loop()
218 {
219 	int timediff;
220 	struct tbuf *tp;
221 
222 	if (wb.ut_line[0] == '\0')	/* It's an init admin process */
223 		return;			/* no connect accounting data here */
224 	switch (wb.ut_type) {
225 	case OLD_TIME:
226 		datetime = wb.ut_xtime;
227 		return;
228 	case NEW_TIME:
229 		if (datetime == 0)
230 			return;
231 		timediff = wb.ut_xtime - datetime;
232 		for (tp = tbuf; tp <= &tbuf[tsize]; tp++)
233 			tp->ttime += timediff;
234 		datetime = 0;
235 		ndates++;
236 		return;
237 	case BOOT_TIME:
238 		upall();
239 	case ACCOUNTING:
240 	case RUN_LVL:
241 		lastime = wb.ut_xtime;
242 		bootshut();
243 		return;
244 	case USER_PROCESS:
245 	case LOGIN_PROCESS:
246 	case INIT_PROCESS:
247 	case DEAD_PROCESS:	/* WHCC mod 3/86  */
248 		update(&tbuf[iline()]);
249 		return;
250 	case EMPTY:
251 		return;
252 	default:
253 		cftime(time_buf, DATE_FMT, &wb.ut_xtime);
254 		fprintf(stderr, "acctcon: invalid type %d for %s %s %s",
255 			wb.ut_type,
256 			wb.ut_name,
257 			wb.ut_line,
258 			time_buf);
259 	}
260 }
261 
262 /*
263  * bootshut: record reboot (or shutdown)
264  * bump count, looking up wb.ut_line in sy table
265  */
266 static void
267 bootshut()
268 {
269 	int i;
270 
271 	for (i = 0; i < nsys && !EQN(wb.ut_line, sy[i].sname); i++)
272 		;
273 	if (i >= nsys) {
274 		if (++nsys > NSYS) {
275 			fprintf(stderr,
276 				"acctcon: recompile with larger NSYS\n");
277 			nsys = NSYS;
278 			return;
279 		}
280 		CPYN(sy[i].sname, wb.ut_line);
281 	}
282 	sy[i].snum++;
283 }
284 
285 /*
286  * iline: look up/enter current line name in tbuf, return index
287  * (used to avoid system dependencies on naming)
288  */
289 static int
290 iline()
291 {
292 	int i;
293 
294 	for (i = 0; i <= tsize; i++)
295 		if (EQN(wb.ut_line, tbuf[i].tline))
296 			return (i);
297 	if (++tsize >= a_tsize) {
298 		a_tsize = a_tsize + A_TSIZE;
299 		if ((tbuf = (struct tbuf *)realloc(tbuf, a_tsize *
300 			sizeof (struct tbuf))) == NULL) {
301 			fprintf(stderr, "acctcon: Cannot reallocate memory\n");
302 			exit(2);
303 		}
304 	}
305 
306 	CPYN(tbuf[tsize].tline, wb.ut_line);
307 	tbuf[tsize].tdev = lintodev(wb.ut_line);
308 	return (tsize);
309 }
310 
311 static void
312 upall()
313 {
314 	struct tbuf *tp;
315 
316 	wb.ut_type = DEAD_PROCESS;	/* fudge a logoff for reboot record. */
317 	for (tp = tbuf; tp <= &tbuf[tsize]; tp++)
318 		update(tp);
319 }
320 
321 /*
322  * update tbuf with new time, write ctmp record for end of session
323  */
324 static void
325 update(struct tbuf *tp)
326 {
327 	time_t	told,	/* last time for tbuf record */
328 		tnew;	/* time of this record */
329 			/* Difference is connect time */
330 
331 	told = tp->ttime;
332 	tnew = wb.ut_xtime;
333 	if (told > tnew) {
334 		cftime(time_buf, DATE_FMT, &told);
335 		fprintf(stderr, "acctcon: bad times: old: %s", time_buf);
336 		cftime(time_buf, DATE_FMT, &tnew);
337 		fprintf(stderr, "new: %s", time_buf);
338 		exitcode = 1;
339 		tp->ttime = tnew;
340 		return;
341 	}
342 	tp->ttime = tnew;
343 	switch (wb.ut_type) {
344 	case USER_PROCESS:
345 		tp->tlsess++;
346 		/*
347 		 * Someone logged in without logging off. Put out record.
348 		 */
349 		if (tp->tname[0] != '\0') {
350 			cb.ct_tty = tp->tdev;
351 			CPYN(cb.ct_name, tp->tname);
352 			cb.ct_uid = namtouid(cb.ct_name);
353 			cb.ct_start = told;
354 			if (pnpsplit(cb.ct_start, (ulong_t)(tnew-told),
355 			    cb.ct_con) == 0) {
356 				fprintf(stderr, "acctcon: could not calculate "
357 				    "prime/non-prime hours\n");
358 				exit(1);
359 			}
360 			enter(&cb);
361 			tp->ttotal += tnew-told;
362 		} else	/* Someone just logged in */
363 			tp->tlon++;
364 		CPYN(tp->tname, wb.ut_name);
365 		break;
366 	case DEAD_PROCESS:
367 		tp->tloff++;
368 		if (tp->tname[0] != '\0') { /* Someone logged off */
369 			/* Set up and print ctmp record */
370 			cb.ct_tty = tp->tdev;
371 			CPYN(cb.ct_name, tp->tname);
372 			cb.ct_uid = namtouid(cb.ct_name);
373 			cb.ct_start = told;
374 			if (pnpsplit(cb.ct_start, (ulong_t)(tnew-told),
375 			    cb.ct_con) == 0) {
376 				fprintf(stderr, "acctcon: could not calculate "
377 				    "prime/non-prime hours\n");
378 				exit(1);
379 			}
380 			enter(&cb);
381 			tp->ttotal += tnew-told;
382 			tp->tname[0] = '\0';
383 		}
384 	}
385 }
386 
387 static void
388 printrep()
389 {
390 	int i;
391 
392 	freopen(report, "w", stdout);
393 	cftime(time_buf, DATE_FMT, &firstime);
394 	printf("from %s", time_buf);
395 	cftime(time_buf, DATE_FMT, &lastime);
396 	printf("to   %s", time_buf);
397 	if (ndates)
398 		printf("%d\tdate change%c\n", ndates, (ndates > 1 ? 's' :
399 		    '\0'));
400 	for (i = 0; i < nsys; i++)
401 		printf("%d\t%.*s\n", sy[i].snum,
402 		    sizeof (sy[i].sname), sy[i].sname);
403 }
404 
405 
406 /*
407  *	print summary of line usage
408  *	accuracy only guaranteed for wtmpx file started fresh
409  */
410 static void
411 printlin()
412 {
413 	struct tbuf *tp;
414 	double ttime;
415 	int tsess, ton, toff;
416 
417 	freopen(replin, "w", stdout);
418 	ttime = 0.0;
419 	tsess = ton = toff = 0;
420 	timet = MINS(lastime-firstime);
421 	printf("TOTAL DURATION IS %.0f MINUTES\n", timet);
422 	printf("LINE         MINUTES  PERCENT  # SESS  # ON  # OFF\n");
423 	qsort((char *)tbuf, tsize + 1, sizeof (tbuf[0]),
424 	    (int (*)(const void *, const void *))tcmp);
425 	for (tp = tbuf; tp <= &tbuf[tsize]; tp++) {
426 		timei = MINS(tp->ttotal);
427 		ttime += timei;
428 		tsess += tp->tlsess;
429 		ton += tp->tlon;
430 		toff += tp->tloff;
431 		printf("%-*.*s %-7.0f  %-7.0f  %-6d  %-4d  %-5d\n",
432 		    OUTPUT_LSZ,
433 		    OUTPUT_LSZ,
434 		    tp->tline,
435 		    timei,
436 		    (timet > 0.)? 100*timei/timet : 0.,
437 		    tp->tlsess,
438 		    tp->tlon,
439 		    tp->tloff);
440 	}
441 	printf("TOTALS       %-7.0f  --       %-6d  %-4d  %-5d\n",
442 	    ttime, tsess, ton, toff);
443 }
444 
445 static int
446 tcmp(struct tbuf *t1, struct tbuf *t2)
447 {
448 	return (strncmp(t1->tline, t2->tline, LSZ));
449 }
450 
451 static int
452 node_compare(const void *node1, const void *node2)
453 {
454 	if (((const struct ctab *)node1)->ct_uid >
455 	    ((const struct ctab *)node2)->ct_uid)
456 		return (1);
457 	else if (((const struct ctab *)node1)->ct_uid <
458 	    ((const struct ctab *)node2)->ct_uid)
459 		return (-1);
460 	else
461 		return (0);
462 }
463 
464 static void
465 enter(struct ctmp *c)
466 {
467 	unsigned i;
468 	int j;
469 	struct ctab **pt;
470 
471 	if ((pctab = (struct ctab *)malloc(sizeof (struct ctab))) == NULL) {
472 		fprintf(stderr, "acctcon: malloc fail!\n");
473 		exit(2);
474 	}
475 
476 	pctab->ct_uid = c->ct_uid;
477 	CPYN(pctab->ct_name, c->ct_name);
478 	pctab->ct_con[0] = c->ct_con[0];
479 	pctab->ct_con[1] = c->ct_con[1];
480 	pctab->ct_sess = 1;
481 
482 	if (*(pt = (struct ctab **)tsearch((void *)pctab, (void **)&root,  \
483 		node_compare)) == NULL) {
484 		fprintf(stderr, "Not enough space available to build tree\n");
485 		exit(1);
486 	}
487 
488 	if (*pt != pctab) {
489 		(*pt)->ct_con[0] += c->ct_con[0];
490 		(*pt)->ct_con[1] += c->ct_con[1];
491 		(*pt)->ct_sess++;
492 		free(pctab);
493 	}
494 
495 }
496 
497 static void
498 print_node(const void *node, VISIT order, int level)
499 {
500 	if (order == postorder || order == leaf) {
501 		tb.ta_uid = (*(struct ctab **)node)->ct_uid;
502 		CPYN(tb.ta_name, (*(struct ctab **)node)->ct_name);
503 		tb.ta_con[0] = ((*(struct ctab **)node)->ct_con[0]) / 60.0;
504 		tb.ta_con[1] = ((*(struct ctab **)node)->ct_con[1]) / 60.0;
505 		tb.ta_sc = (*(struct ctab **)node)->ct_sess;
506 		fwrite(&tb, sizeof (tb), 1, stdout);
507 	}
508 }
509 
510 static void
511 output()
512 {
513 	twalk((struct ctab *)root, print_node);
514 }
515