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 31 /* 32 * acctcon1 [-p] [-t] [-l file] [-o file] <wtmpx-file >ctmp-file 33 * -p print input only, no processing 34 * -t test mode: use latest time found in input, rather than 35 * current time when computing times of lines still on 36 * (only way to get repeatable data from old files) 37 * -l file causes output of line usage summary 38 * -o file causes first/last/reboots report to be written to file 39 * reads input (normally /var/adm/wtmpx), produces 40 * list of sessions, sorted by ending time in ctmp.h/ascii format 41 * A_TSIZE is max # distinct ttys 42 */ 43 44 #include <sys/types.h> 45 #include "acctdef.h" 46 #include <stdio.h> 47 #include <ctype.h> 48 #include <time.h> 49 #include <utmpx.h> 50 #include <locale.h> 51 #include <stdlib.h> 52 53 int a_tsize = A_TSIZE; 54 int tsize = -1; /* used slots in tbuf table */ 55 struct utmpx wb; /* record structure read into */ 56 struct ctmp cb; /* record structure written out of */ 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 #define DATE_FMT "%a %b %e %H:%M:%S %Y\n" 70 int nsys; 71 struct sys { 72 char sname[LSZ]; /* reasons for ACCOUNTING records */ 73 char snum; /* number of times encountered */ 74 } sy[NSYS]; 75 76 time_t datetime; /* old time if date changed, otherwise 0 */ 77 time_t firstime; 78 time_t lastime; 79 int ndates; /* number of times date changed */ 80 int exitcode; 81 char *report = NULL; 82 char *replin = NULL; 83 int printonly; 84 int tflag; 85 86 static char time_buf[50]; 87 uid_t namtouid(); 88 dev_t lintodev(); 89 static size_t wread(void); 90 static int valid(void); 91 static void fixup(FILE *); 92 static void loop(void); 93 static void bootshut(void); 94 static int iline(void); 95 static void upall(void); 96 static void update(struct tbuf *); 97 static void printrep(void); 98 static void printlin(void); 99 static void prctmp(struct ctmp *); 100 101 int 102 main(int argc, char **argv) 103 { 104 char *prog = argv[0]; 105 106 (void)setlocale(LC_ALL, ""); 107 while (--argc > 0 && **++argv == '-') 108 switch(*++*argv) { 109 case 'l': 110 if (--argc > 0) 111 replin = *++argv; 112 continue; 113 case 'o': 114 if (--argc > 0) 115 report = *++argv; 116 continue; 117 case 'p': 118 printonly++; 119 continue; 120 case 't': 121 tflag++; 122 continue; 123 default: 124 fprintf(stderr, "usage: %s [-p] [-t] [-l lineuse] [-o reboot]\n", prog); 125 exit(1); 126 127 } 128 129 if ((tbuf = (struct tbuf *) calloc(a_tsize, 130 sizeof (struct tbuf))) == NULL) { 131 fprintf(stderr, "acctcon1: Cannot allocate memory\n"); 132 exit(3); 133 } 134 135 if (printonly) { 136 while (wread()) { 137 if (valid()) { 138 printf("%.*s\t%.*s\t%lu", 139 sizeof (wb.ut_line), 140 wb.ut_line, 141 sizeof (wb.ut_name), 142 wb.ut_name, 143 wb.ut_xtime); 144 cftime(time_buf, DATE_FMT, &wb.ut_xtime); 145 printf("\t%s", time_buf); 146 } else 147 fixup(stdout); 148 149 } 150 exit(exitcode); 151 } 152 153 while (wread()) { 154 if (firstime == 0) 155 firstime = wb.ut_xtime; 156 if (valid()) 157 loop(); 158 else 159 fixup(stderr); 160 } 161 wb.ut_name[0] = '\0'; 162 strcpy(wb.ut_line, "acctcon1"); 163 wb.ut_type = ACCOUNTING; 164 if (tflag) 165 wb.ut_xtime = lastime; 166 else 167 time(&wb.ut_xtime); 168 loop(); 169 if (report != NULL) 170 printrep(); 171 if (replin != NULL) 172 printlin(); 173 exit(exitcode); 174 } 175 176 static size_t 177 wread() 178 { 179 return (fread(&wb, sizeof(wb), 1, stdin) == 1); 180 181 } 182 183 /* 184 * valid: check input wtmp record, return 1 if looks OK 185 */ 186 static int 187 valid() 188 { 189 int i, c; 190 191 /* XPG say that user names should not start with a "-". */ 192 if ((c = wb.ut_name[0]) == '-') 193 return(0); 194 195 for (i = 0; i < NSZ; i++) { 196 c = wb.ut_name[i]; 197 if (isalnum(c) || c == '$' || c == ' ' || c == '_' || c == '-') 198 continue; 199 else if (c == '\0') 200 break; 201 else 202 return(0); 203 } 204 205 if((wb.ut_type >= EMPTY) && (wb.ut_type <= UTMAXTYPE)) 206 return(1); 207 208 return(0); 209 } 210 211 /* 212 * fixup assumes that V6 wtmp (16 bytes long) is mixed in with 213 * V7 records (20 bytes each) 214 * 215 * Starting with Release 5.0 of UNIX, this routine will no 216 * longer reset the read pointer. This has a snowball effect 217 * On the following records until the offset corrects itself. 218 * If a message is printed from here, it should be regarded as 219 * a bad record and not as a V6 record. 220 */ 221 static void 222 fixup(FILE *stream) 223 { 224 fprintf(stream, "bad wtmpx: offset %lu.\n", ftell(stdin)-sizeof(wb)); 225 fprintf(stream, "bad record is: %.*s\t%.*s\t%lu", 226 sizeof (wb.ut_line), 227 wb.ut_line, 228 sizeof (wb.ut_name), 229 wb.ut_name, 230 wb.ut_xtime); 231 cftime(time_buf, DATE_FMT, &wb.ut_xtime); 232 fprintf(stream, "\t%s", time_buf); 233 #ifdef V6 234 fseek(stdin, (long)-4, 1); 235 #endif 236 exitcode = 1; 237 } 238 239 static void 240 loop() 241 { 242 int timediff; 243 struct tbuf *tp; 244 245 if(wb.ut_line[0] == '\0' ) /* It's an init admin process */ 246 return; /* no connect accounting data here */ 247 switch(wb.ut_type) { 248 case OLD_TIME: 249 datetime = wb.ut_xtime; 250 return; 251 case NEW_TIME: 252 if(datetime == 0) 253 return; 254 timediff = wb.ut_xtime - datetime; 255 for (tp = tbuf; tp <= &tbuf[tsize]; tp++) 256 tp->ttime += timediff; 257 datetime = 0; 258 ndates++; 259 return; 260 case BOOT_TIME: 261 upall(); 262 /* FALLTHROUGH */ 263 case ACCOUNTING: 264 case RUN_LVL: 265 lastime = wb.ut_xtime; 266 bootshut(); 267 return; 268 case USER_PROCESS: 269 case LOGIN_PROCESS: 270 case INIT_PROCESS: 271 case DEAD_PROCESS: 272 update(&tbuf[iline()]); 273 return; 274 case EMPTY: 275 return; 276 default: 277 cftime(time_buf, DATE_FMT, &wb.ut_xtime); 278 fprintf(stderr, "acctcon1: invalid type %d for %s %s %s", 279 wb.ut_type, 280 wb.ut_name, 281 wb.ut_line, 282 time_buf); 283 } 284 } 285 286 /* 287 * bootshut: record reboot (or shutdown) 288 * bump count, looking up wb.ut_line in sy table 289 */ 290 static void 291 bootshut() 292 { 293 int i; 294 295 for (i = 0; i < nsys && !EQN(wb.ut_line, sy[i].sname); i++) 296 ; 297 if (i >= nsys) { 298 if (++nsys > NSYS) { 299 fprintf(stderr, 300 "acctcon1: recompile with larger NSYS\n"); 301 nsys = NSYS; 302 return; 303 } 304 CPYN(sy[i].sname, wb.ut_line); 305 } 306 sy[i].snum++; 307 } 308 309 /* 310 * iline: look up/enter current line name in tbuf, return index 311 * (used to avoid system dependencies on naming) 312 */ 313 static int 314 iline() 315 { 316 int i; 317 318 for (i = 0; i <= tsize; i++) 319 if (EQN(wb.ut_line, tbuf[i].tline)) 320 return(i); 321 if (++tsize >= a_tsize) { 322 a_tsize = a_tsize + A_TSIZE; 323 if ((tbuf = (struct tbuf *) realloc(tbuf, a_tsize * 324 sizeof (struct tbuf))) == NULL) { 325 fprintf(stderr, "acctcon1: Cannot reallocate memory\n"); 326 exit(2); 327 } 328 } 329 330 CPYN(tbuf[tsize].tline, wb.ut_line); 331 tbuf[tsize].tdev = lintodev(wb.ut_line); 332 return(tsize); 333 } 334 335 static void 336 upall() 337 { 338 struct tbuf *tp; 339 340 wb.ut_type = INIT_PROCESS; /* fudge a logoff for reboot record */ 341 for (tp = tbuf; tp <= &tbuf[tsize]; tp++) 342 update(tp); 343 } 344 345 /* 346 * update tbuf with new time, write ctmp record for end of session 347 */ 348 static void 349 update(struct tbuf *tp) 350 { 351 time_t told, /* last time for tbuf record */ 352 tnew; /* time of this record */ 353 /* Difference is connect time */ 354 355 told = tp->ttime; 356 tnew = wb.ut_xtime; 357 cftime(time_buf, DATE_FMT, &told); 358 fprintf(stderr, "The old time is: %s", time_buf); 359 cftime(time_buf, DATE_FMT, &tnew); 360 fprintf(stderr, "the new time is: %s", time_buf); 361 if (told > tnew) { 362 cftime(time_buf, DATE_FMT, &told); 363 fprintf(stderr, "acctcon1: bad times: old: %s", time_buf); 364 cftime(time_buf, DATE_FMT, &tnew); 365 fprintf(stderr, "new: %s", time_buf); 366 exitcode = 1; 367 tp->ttime = tnew; 368 return; 369 } 370 tp->ttime = tnew; 371 switch(wb.ut_type) { 372 case USER_PROCESS: 373 tp->tlsess++; 374 if(tp->tname[0] != '\0') { /* Someone logged in without */ 375 /* logging off. Put out record. */ 376 cb.ct_tty = tp->tdev; 377 CPYN(cb.ct_name, tp->tname); 378 cb.ct_uid = namtouid(cb.ct_name); 379 cb.ct_start = told; 380 if (pnpsplit(cb.ct_start, (ulong_t)(tnew-told), 381 cb.ct_con) == 0) { 382 fprintf(stderr, "acctcon1: could not calculate prime/non-prime hours\n"); 383 384 exit(1); 385 } 386 prctmp(&cb); 387 tp->ttotal += tnew-told; 388 } 389 else /* Someone just logged in */ 390 tp->tlon++; 391 CPYN(tp->tname, wb.ut_name); 392 break; 393 case INIT_PROCESS: 394 case LOGIN_PROCESS: 395 case DEAD_PROCESS: 396 tp->tloff++; 397 if(tp->tname[0] != '\0') { /* Someone logged off */ 398 /* Set up and print ctmp record */ 399 cb.ct_tty = tp->tdev; 400 CPYN(cb.ct_name, tp->tname); 401 cb.ct_uid = namtouid(cb.ct_name); 402 cb.ct_start = told; 403 if (pnpsplit(cb.ct_start, (ulong_t)(tnew-told), 404 cb.ct_con) == 0) { 405 fprintf(stderr, "acctcon1: could not calculate prime/non-prime hours\n"); 406 exit(1); 407 } 408 prctmp(&cb); 409 tp->ttotal += tnew-told; 410 tp->tname[0] = '\0'; 411 } 412 } 413 } 414 415 static void 416 printrep() 417 { 418 int i; 419 420 freopen(report, "w", stdout); 421 cftime(time_buf, DATE_FMT, &firstime); 422 printf("from %s", time_buf); 423 cftime(time_buf, DATE_FMT, &lastime); 424 printf("to %s", time_buf); 425 if (ndates) 426 printf("%d\tdate change%c\n",ndates,(ndates>1 ? 's' : '\0')); 427 for (i = 0; i < nsys; i++) 428 printf("%d\t%.*s\n", sy[i].snum, 429 sizeof (sy[i].sname), sy[i].sname); 430 } 431 432 /* 433 * print summary of line usage 434 * accuracy only guaranteed for wtmpx file started fresh 435 */ 436 static void 437 printlin() 438 { 439 struct tbuf *tp; 440 double timet, timei; 441 double ttime; 442 int tsess, ton, toff; 443 444 freopen(replin, "w", stdout); 445 ttime = 0.0; 446 tsess = ton = toff = 0; 447 timet = MINS(lastime-firstime); 448 printf("TOTAL DURATION IS %.0f MINUTES\n", timet); 449 printf("LINE MINUTES PERCENT # SESS # ON # OFF\n"); 450 for (tp = tbuf; tp <= &tbuf[tsize]; tp++) { 451 timei = MINS(tp->ttotal); 452 ttime += timei; 453 tsess += tp->tlsess; 454 ton += tp->tlon; 455 toff += tp->tloff; 456 printf("%-*.*s %-7.0f %-7.0f %-6d %-4d %-5d\n", 457 OUTPUT_LSZ, 458 OUTPUT_LSZ, 459 tp->tline, 460 timei, 461 (timet > 0.)? 100*timei/timet : 0., 462 tp->tlsess, 463 tp->tlon, 464 tp->tloff); 465 } 466 printf("TOTALS %-7.0f -- %-6d %-4d %-5d\n", 467 ttime, tsess, ton, toff); 468 } 469 470 static void 471 prctmp(struct ctmp *t) 472 { 473 474 printf("%u\t%ld\t%.*s\t%lu\t%lu\t%lu", 475 t->ct_tty, 476 t->ct_uid, 477 OUTPUT_NSZ, 478 t->ct_name, 479 t->ct_con[0], 480 t->ct_con[1], 481 t->ct_start); 482 cftime(time_buf, DATE_FMT, &t->ct_start); 483 printf("\t%s", time_buf); 484 } 485