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