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