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