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
main(int argc,char ** argv)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
valid()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
fixup(FILE * stream)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
loop()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
bootshut()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
iline()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
upall()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
update(struct tbuf * tp)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
printrep()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
printlin()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
tcmp(struct tbuf * t1,struct tbuf * t2)447 tcmp(struct tbuf *t1, struct tbuf *t2)
448 {
449 return (strncmp(t1->tline, t2->tline, LSZ));
450 }
451
452 static int
node_compare(const void * node1,const void * node2)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
enter(struct ctmp * c)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
print_node(const void * node,VISIT order,int level)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
output()512 output()
513 {
514 twalk((struct ctab *)root, print_node);
515 }
516