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
30 /*
31 * acctcon [-l file] [-o file] <wtmpx-file
32 * -l file causes output of line usage summary
33 * -o file causes first/last/reboots report to be written to file
34 * reads input (normally /var/adm/wtmpx), produces
35 * list of sessions, sorted by ending time in tacct.h format
36 */
37
38 #include <stdio.h>
39 #include <sys/types.h>
40 #include <sys/param.h>
41 #include "acctdef.h"
42 #include <ctype.h>
43 #include <time.h>
44 #include <utmpx.h>
45 #include <locale.h>
46 #include <string.h>
47 #include <search.h>
48 #include <stdlib.h>
49
50 int a_tsize = A_TSIZE;
51 int tsize = -1; /* highest index of used slot in tbuf table */
52 static int csize;
53 struct utmpx wb; /* record structure read into */
54 struct ctmp cb; /* record structure written out of */
55 struct tacct tb;
56 double timet, timei;
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 struct ctab {
70 uid_t ct_uid;
71 char ct_name[NSZ];
72 long ct_con[2];
73 ushort_t ct_sess;
74 } *pctab;
75
76 int nsys;
77 struct sys {
78 char sname[LSZ]; /* reasons for ACCOUNTING records */
79 char snum; /* number of times encountered */
80 } sy[NSYS];
81
82 static char time_buf[50];
83 time_t datetime; /* old time if date changed, otherwise 0 */
84 time_t firstime;
85 time_t lastime;
86 int ndates; /* number of times date changed */
87 int exitcode;
88 char *report = NULL;
89 char *replin = NULL;
90
91 uid_t namtouid();
92 dev_t lintodev();
93 static int valid(void);
94 static void fixup(FILE *);
95 static void loop(void);
96 static void bootshut(void);
97 static int iline(void);
98 static void upall(void);
99 static void update(struct tbuf *);
100 static void printrep(void);
101 static void printlin(void);
102 static int tcmp(struct tbuf *, struct tbuf *);
103 static int node_compare(const void *, const void *);
104 static void enter(struct ctmp *);
105 static void print_node(const void *, VISIT, int);
106 static void output(void);
107
108 extern char *optarg;
109 extern int optind;
110
111 void **root = NULL;
112
113 int
main(int argc,char ** argv)114 main(int argc, char **argv)
115 {
116 int c;
117
118 (void) setlocale(LC_ALL, "");
119 while ((c = getopt(argc, argv, "l:o:")) != EOF)
120 switch (c) {
121 case 'l':
122 replin = optarg;
123 break;
124 case 'o':
125 report = optarg;
126 break;
127 case '?':
128 fprintf(stderr, "usage: %s [-l lineuse] "
129 "[-o reboot]\n", argv[0]);
130 exit(1);
131 }
132
133 if ((tbuf = (struct tbuf *)calloc(a_tsize,
134 sizeof (struct tbuf))) == NULL) {
135 fprintf(stderr, "acctcon: Cannot allocate memory\n");
136 exit(3);
137 }
138
139 /*
140 * XXX - fixme - need a good way of getting the fd that getutxent would
141 * use to access wtmpx, so we can convert this read of stdin to use
142 * the APIs and remove the dependence on the existence of the file.
143 */
144 while (fread(&wb, sizeof (wb), 1, stdin) == 1) {
145 if (firstime == 0)
146 firstime = wb.ut_xtime;
147 if (valid())
148 loop();
149 else
150 fixup(stderr);
151 }
152 wb.ut_name[0] = '\0';
153 strcpy(wb.ut_line, "acctcon");
154 wb.ut_type = ACCOUNTING;
155 wb.ut_xtime = lastime;
156 loop();
157
158 output();
159
160 if (report != NULL)
161 printrep();
162 if (replin != NULL)
163 printlin();
164
165 exit(exitcode);
166 }
167
168
169 /*
170 * valid: check input wtmpx record, return 1 if looks OK
171 */
172 static int
valid()173 valid()
174 {
175 int i, c;
176
177 /* XPG say that user names should not start with a "-" */
178 if ((c = wb.ut_name[0]) == '-')
179 return (0);
180
181 for (i = 0; i < NSZ; i++) {
182 c = wb.ut_name[i];
183 if (isalnum(c) || c == '$' || c == ' ' || c == '.' ||
184 c == '_' || c == '-')
185 continue;
186 else if (c == '\0')
187 break;
188 else
189 return (0);
190 }
191
192 if ((wb.ut_type >= EMPTY) && (wb.ut_type <= UTMAXTYPE))
193 return (1);
194
195 return (0);
196 }
197
198 static void
fixup(FILE * stream)199 fixup(FILE *stream)
200 {
201 fprintf(stream, "bad wtmpx: offset %lu.\n", ftell(stdin)-sizeof (wb));
202 fprintf(stream, "bad record is: %.*s\t%.*s\t%lu",
203 sizeof (wb.ut_line),
204 wb.ut_line,
205 sizeof (wb.ut_name),
206 wb.ut_name,
207 wb.ut_xtime);
208 cftime(time_buf, DATE_FMT, &wb.ut_xtime);
209 fprintf(stream, "\t%s", time_buf);
210 exitcode = 1;
211 }
212
213 static void
loop()214 loop()
215 {
216 int timediff;
217 struct tbuf *tp;
218
219 if (wb.ut_line[0] == '\0') /* It's an init admin process */
220 return; /* no connect accounting data here */
221 switch (wb.ut_type) {
222 case OLD_TIME:
223 datetime = wb.ut_xtime;
224 return;
225 case NEW_TIME:
226 if (datetime == 0)
227 return;
228 timediff = wb.ut_xtime - datetime;
229 for (tp = tbuf; tp <= &tbuf[tsize]; tp++)
230 tp->ttime += timediff;
231 datetime = 0;
232 ndates++;
233 return;
234 case DOWN_TIME:
235 return;
236 case BOOT_TIME:
237 upall();
238 /* FALLTHROUGH */
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
bootshut()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
iline()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
upall()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
update(struct tbuf * tp)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
printrep()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
printlin()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
tcmp(struct tbuf * t1,struct tbuf * t2)446 tcmp(struct tbuf *t1, struct tbuf *t2)
447 {
448 return (strncmp(t1->tline, t2->tline, LSZ));
449 }
450
451 static int
node_compare(const void * node1,const void * node2)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
enter(struct ctmp * c)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
print_node(const void * node,VISIT order,int level)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
output()511 output()
512 {
513 twalk((struct ctab *)root, print_node);
514 }
515