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 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 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 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 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 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