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 2004 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 52 int a_tsize = A_TSIZE; 53 int tsize = -1; /* highest index of used slot in tbuf table */ 54 static 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 BOOT_TIME: 237 upall(); 238 case ACCOUNTING: 239 case RUN_LVL: 240 lastime = wb.ut_xtime; 241 bootshut(); 242 return; 243 case USER_PROCESS: 244 case LOGIN_PROCESS: 245 case INIT_PROCESS: 246 case DEAD_PROCESS: /* WHCC mod 3/86 */ 247 update(&tbuf[iline()]); 248 return; 249 case EMPTY: 250 return; 251 default: 252 cftime(time_buf, DATE_FMT, &wb.ut_xtime); 253 fprintf(stderr, "acctcon: invalid type %d for %s %s %s", 254 wb.ut_type, 255 wb.ut_name, 256 wb.ut_line, 257 time_buf); 258 } 259 } 260 261 /* 262 * bootshut: record reboot (or shutdown) 263 * bump count, looking up wb.ut_line in sy table 264 */ 265 static void 266 bootshut() 267 { 268 int i; 269 270 for (i = 0; i < nsys && !EQN(wb.ut_line, sy[i].sname); i++) 271 ; 272 if (i >= nsys) { 273 if (++nsys > NSYS) { 274 fprintf(stderr, 275 "acctcon: recompile with larger NSYS\n"); 276 nsys = NSYS; 277 return; 278 } 279 CPYN(sy[i].sname, wb.ut_line); 280 } 281 sy[i].snum++; 282 } 283 284 /* 285 * iline: look up/enter current line name in tbuf, return index 286 * (used to avoid system dependencies on naming) 287 */ 288 static int 289 iline() 290 { 291 int i; 292 293 for (i = 0; i <= tsize; i++) 294 if (EQN(wb.ut_line, tbuf[i].tline)) 295 return (i); 296 if (++tsize >= a_tsize) { 297 a_tsize = a_tsize + A_TSIZE; 298 if ((tbuf = (struct tbuf *)realloc(tbuf, a_tsize * 299 sizeof (struct tbuf))) == NULL) { 300 fprintf(stderr, "acctcon: Cannot reallocate memory\n"); 301 exit(2); 302 } 303 } 304 305 CPYN(tbuf[tsize].tline, wb.ut_line); 306 tbuf[tsize].tdev = lintodev(wb.ut_line); 307 return (tsize); 308 } 309 310 static void 311 upall() 312 { 313 struct tbuf *tp; 314 315 wb.ut_type = DEAD_PROCESS; /* fudge a logoff for reboot record. */ 316 for (tp = tbuf; tp <= &tbuf[tsize]; tp++) 317 update(tp); 318 } 319 320 /* 321 * update tbuf with new time, write ctmp record for end of session 322 */ 323 static void 324 update(struct tbuf *tp) 325 { 326 time_t told, /* last time for tbuf record */ 327 tnew; /* time of this record */ 328 /* Difference is connect time */ 329 330 told = tp->ttime; 331 tnew = wb.ut_xtime; 332 if (told > tnew) { 333 cftime(time_buf, DATE_FMT, &told); 334 fprintf(stderr, "acctcon: bad times: old: %s", time_buf); 335 cftime(time_buf, DATE_FMT, &tnew); 336 fprintf(stderr, "new: %s", time_buf); 337 exitcode = 1; 338 tp->ttime = tnew; 339 return; 340 } 341 tp->ttime = tnew; 342 switch (wb.ut_type) { 343 case USER_PROCESS: 344 tp->tlsess++; 345 /* 346 * Someone logged in without logging off. Put out record. 347 */ 348 if (tp->tname[0] != '\0') { 349 cb.ct_tty = tp->tdev; 350 CPYN(cb.ct_name, tp->tname); 351 cb.ct_uid = namtouid(cb.ct_name); 352 cb.ct_start = told; 353 if (pnpsplit(cb.ct_start, (ulong_t)(tnew-told), 354 cb.ct_con) == 0) { 355 fprintf(stderr, "acctcon: could not calculate " 356 "prime/non-prime hours\n"); 357 exit(1); 358 } 359 enter(&cb); 360 tp->ttotal += tnew-told; 361 } else /* Someone just logged in */ 362 tp->tlon++; 363 CPYN(tp->tname, wb.ut_name); 364 break; 365 case DEAD_PROCESS: 366 tp->tloff++; 367 if (tp->tname[0] != '\0') { /* Someone logged off */ 368 /* Set up and print ctmp record */ 369 cb.ct_tty = tp->tdev; 370 CPYN(cb.ct_name, tp->tname); 371 cb.ct_uid = namtouid(cb.ct_name); 372 cb.ct_start = told; 373 if (pnpsplit(cb.ct_start, (ulong_t)(tnew-told), 374 cb.ct_con) == 0) { 375 fprintf(stderr, "acctcon: could not calculate " 376 "prime/non-prime hours\n"); 377 exit(1); 378 } 379 enter(&cb); 380 tp->ttotal += tnew-told; 381 tp->tname[0] = '\0'; 382 } 383 } 384 } 385 386 static void 387 printrep() 388 { 389 int i; 390 391 freopen(report, "w", stdout); 392 cftime(time_buf, DATE_FMT, &firstime); 393 printf("from %s", time_buf); 394 cftime(time_buf, DATE_FMT, &lastime); 395 printf("to %s", time_buf); 396 if (ndates) 397 printf("%d\tdate change%c\n", ndates, (ndates > 1 ? 's' : 398 '\0')); 399 for (i = 0; i < nsys; i++) 400 printf("%d\t%.*s\n", sy[i].snum, 401 sizeof (sy[i].sname), sy[i].sname); 402 } 403 404 405 /* 406 * print summary of line usage 407 * accuracy only guaranteed for wtmpx file started fresh 408 */ 409 static void 410 printlin() 411 { 412 struct tbuf *tp; 413 double ttime; 414 int tsess, ton, toff; 415 416 freopen(replin, "w", stdout); 417 ttime = 0.0; 418 tsess = ton = toff = 0; 419 timet = MINS(lastime-firstime); 420 printf("TOTAL DURATION IS %.0f MINUTES\n", timet); 421 printf("LINE MINUTES PERCENT # SESS # ON # OFF\n"); 422 qsort((char *)tbuf, tsize + 1, sizeof (tbuf[0]), tcmp); 423 for (tp = tbuf; tp <= &tbuf[tsize]; tp++) { 424 timei = MINS(tp->ttotal); 425 ttime += timei; 426 tsess += tp->tlsess; 427 ton += tp->tlon; 428 toff += tp->tloff; 429 printf("%-*.*s %-7.0f %-7.0f %-6d %-4d %-5d\n", 430 OUTPUT_LSZ, 431 OUTPUT_LSZ, 432 tp->tline, 433 timei, 434 (timet > 0.)? 100*timei/timet : 0., 435 tp->tlsess, 436 tp->tlon, 437 tp->tloff); 438 } 439 printf("TOTALS %-7.0f -- %-6d %-4d %-5d\n", 440 ttime, tsess, ton, toff); 441 } 442 443 static int 444 tcmp(struct tbuf *t1, struct tbuf *t2) 445 { 446 return (strncmp(t1->tline, t2->tline, LSZ)); 447 } 448 449 static int 450 node_compare(const void *node1, const void *node2) 451 { 452 if (((const struct ctab *)node1)->ct_uid > 453 ((const struct ctab *)node2)->ct_uid) 454 return (1); 455 else if (((const struct ctab *)node1)->ct_uid < 456 ((const struct ctab *)node2)->ct_uid) 457 return (-1); 458 else 459 return (0); 460 } 461 462 static void 463 enter(struct ctmp *c) 464 { 465 unsigned i; 466 int j; 467 struct ctab **pt; 468 469 if ((pctab = (struct ctab *)malloc(sizeof (struct ctab))) == NULL) { 470 fprintf(stderr, "acctcon: malloc fail!\n"); 471 exit(2); 472 } 473 474 pctab->ct_uid = c->ct_uid; 475 CPYN(pctab->ct_name, c->ct_name); 476 pctab->ct_con[0] = c->ct_con[0]; 477 pctab->ct_con[1] = c->ct_con[1]; 478 pctab->ct_sess = 1; 479 480 if (*(pt = (struct ctab **)tsearch((void *)pctab, (void **)&root, \ 481 node_compare)) == NULL) { 482 fprintf(stderr, "Not enough space available to build tree\n"); 483 exit(1); 484 } 485 486 if (*pt != pctab) { 487 (*pt)->ct_con[0] += c->ct_con[0]; 488 (*pt)->ct_con[1] += c->ct_con[1]; 489 (*pt)->ct_sess++; 490 free(pctab); 491 } 492 493 } 494 495 static void 496 print_node(const void *node, VISIT order, int level) 497 { 498 if (order == postorder || order == leaf) { 499 tb.ta_uid = (*(struct ctab **)node)->ct_uid; 500 CPYN(tb.ta_name, (*(struct ctab **)node)->ct_name); 501 tb.ta_con[0] = ((*(struct ctab **)node)->ct_con[0]) / 60.0; 502 tb.ta_con[1] = ((*(struct ctab **)node)->ct_con[1]) / 60.0; 503 tb.ta_sc = (*(struct ctab **)node)->ct_sess; 504 fwrite(&tb, sizeof (tb), 1, stdout); 505 } 506 } 507 508 static void 509 output() 510 { 511 twalk((struct ctab *)root, print_node); 512 } 513