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