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 #pragma ident "%Z%%M% %I% %E% SMI" 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 case ACCOUNTING: 263 case RUN_LVL: 264 lastime = wb.ut_xtime; 265 bootshut(); 266 return; 267 case USER_PROCESS: 268 case LOGIN_PROCESS: 269 case INIT_PROCESS: 270 case DEAD_PROCESS: 271 update(&tbuf[iline()]); 272 return; 273 case EMPTY: 274 return; 275 default: 276 cftime(time_buf, DATE_FMT, &wb.ut_xtime); 277 fprintf(stderr, "acctcon1: invalid type %d for %s %s %s", 278 wb.ut_type, 279 wb.ut_name, 280 wb.ut_line, 281 time_buf); 282 } 283 } 284 285 /* 286 * bootshut: record reboot (or shutdown) 287 * bump count, looking up wb.ut_line in sy table 288 */ 289 static void 290 bootshut() 291 { 292 int i; 293 294 for (i = 0; i < nsys && !EQN(wb.ut_line, sy[i].sname); i++) 295 ; 296 if (i >= nsys) { 297 if (++nsys > NSYS) { 298 fprintf(stderr, 299 "acctcon1: recompile with larger NSYS\n"); 300 nsys = NSYS; 301 return; 302 } 303 CPYN(sy[i].sname, wb.ut_line); 304 } 305 sy[i].snum++; 306 } 307 308 /* 309 * iline: look up/enter current line name in tbuf, return index 310 * (used to avoid system dependencies on naming) 311 */ 312 static int 313 iline() 314 { 315 int i; 316 317 for (i = 0; i <= tsize; i++) 318 if (EQN(wb.ut_line, tbuf[i].tline)) 319 return(i); 320 if (++tsize >= a_tsize) { 321 a_tsize = a_tsize + A_TSIZE; 322 if ((tbuf = (struct tbuf *) realloc(tbuf, a_tsize * 323 sizeof (struct tbuf))) == NULL) { 324 fprintf(stderr, "acctcon1: Cannot reallocate memory\n"); 325 exit(2); 326 } 327 } 328 329 CPYN(tbuf[tsize].tline, wb.ut_line); 330 tbuf[tsize].tdev = lintodev(wb.ut_line); 331 return(tsize); 332 } 333 334 static void 335 upall() 336 { 337 struct tbuf *tp; 338 339 wb.ut_type = INIT_PROCESS; /* fudge a logoff for reboot record */ 340 for (tp = tbuf; tp <= &tbuf[tsize]; tp++) 341 update(tp); 342 } 343 344 /* 345 * update tbuf with new time, write ctmp record for end of session 346 */ 347 static void 348 update(struct tbuf *tp) 349 { 350 time_t told, /* last time for tbuf record */ 351 tnew; /* time of this record */ 352 /* Difference is connect time */ 353 354 told = tp->ttime; 355 tnew = wb.ut_xtime; 356 cftime(time_buf, DATE_FMT, &told); 357 fprintf(stderr, "The old time is: %s", time_buf); 358 cftime(time_buf, DATE_FMT, &tnew); 359 fprintf(stderr, "the new time is: %s", time_buf); 360 if (told > tnew) { 361 cftime(time_buf, DATE_FMT, &told); 362 fprintf(stderr, "acctcon1: bad times: old: %s", time_buf); 363 cftime(time_buf, DATE_FMT, &tnew); 364 fprintf(stderr, "new: %s", time_buf); 365 exitcode = 1; 366 tp->ttime = tnew; 367 return; 368 } 369 tp->ttime = tnew; 370 switch(wb.ut_type) { 371 case USER_PROCESS: 372 tp->tlsess++; 373 if(tp->tname[0] != '\0') { /* Someone logged in without */ 374 /* logging off. Put out record. */ 375 cb.ct_tty = tp->tdev; 376 CPYN(cb.ct_name, tp->tname); 377 cb.ct_uid = namtouid(cb.ct_name); 378 cb.ct_start = told; 379 if (pnpsplit(cb.ct_start, (ulong_t)(tnew-told), 380 cb.ct_con) == 0) { 381 fprintf(stderr, "acctcon1: could not calculate prime/non-prime hours\n"); 382 383 exit(1); 384 } 385 prctmp(&cb); 386 tp->ttotal += tnew-told; 387 } 388 else /* Someone just logged in */ 389 tp->tlon++; 390 CPYN(tp->tname, wb.ut_name); 391 break; 392 case INIT_PROCESS: 393 case LOGIN_PROCESS: 394 case DEAD_PROCESS: 395 tp->tloff++; 396 if(tp->tname[0] != '\0') { /* Someone logged off */ 397 /* Set up and print ctmp record */ 398 cb.ct_tty = tp->tdev; 399 CPYN(cb.ct_name, tp->tname); 400 cb.ct_uid = namtouid(cb.ct_name); 401 cb.ct_start = told; 402 if (pnpsplit(cb.ct_start, (ulong_t)(tnew-told), 403 cb.ct_con) == 0) { 404 fprintf(stderr, "acctcon1: could not calculate prime/non-prime hours\n"); 405 exit(1); 406 } 407 prctmp(&cb); 408 tp->ttotal += tnew-told; 409 tp->tname[0] = '\0'; 410 } 411 } 412 } 413 414 static void 415 printrep() 416 { 417 int i; 418 419 freopen(report, "w", stdout); 420 cftime(time_buf, DATE_FMT, &firstime); 421 printf("from %s", time_buf); 422 cftime(time_buf, DATE_FMT, &lastime); 423 printf("to %s", time_buf); 424 if (ndates) 425 printf("%d\tdate change%c\n",ndates,(ndates>1 ? 's' : '\0')); 426 for (i = 0; i < nsys; i++) 427 printf("%d\t%.*s\n", sy[i].snum, 428 sizeof (sy[i].sname), sy[i].sname); 429 } 430 431 /* 432 * print summary of line usage 433 * accuracy only guaranteed for wtmpx file started fresh 434 */ 435 static void 436 printlin() 437 { 438 struct tbuf *tp; 439 double timet, timei; 440 double ttime; 441 int tsess, ton, toff; 442 443 freopen(replin, "w", stdout); 444 ttime = 0.0; 445 tsess = ton = toff = 0; 446 timet = MINS(lastime-firstime); 447 printf("TOTAL DURATION IS %.0f MINUTES\n", timet); 448 printf("LINE MINUTES PERCENT # SESS # ON # OFF\n"); 449 for (tp = tbuf; tp <= &tbuf[tsize]; tp++) { 450 timei = MINS(tp->ttotal); 451 ttime += timei; 452 tsess += tp->tlsess; 453 ton += tp->tlon; 454 toff += tp->tloff; 455 printf("%-*.*s %-7.0f %-7.0f %-6d %-4d %-5d\n", 456 OUTPUT_LSZ, 457 OUTPUT_LSZ, 458 tp->tline, 459 timei, 460 (timet > 0.)? 100*timei/timet : 0., 461 tp->tlsess, 462 tp->tlon, 463 tp->tloff); 464 } 465 printf("TOTALS %-7.0f -- %-6d %-4d %-5d\n", 466 ttime, tsess, ton, toff); 467 } 468 469 static void 470 prctmp(struct ctmp *t) 471 { 472 473 printf("%u\t%ld\t%.*s\t%lu\t%lu\t%lu", 474 t->ct_tty, 475 t->ct_uid, 476 OUTPUT_NSZ, 477 t->ct_name, 478 t->ct_con[0], 479 t->ct_con[1], 480 t->ct_start); 481 cftime(time_buf, DATE_FMT, &t->ct_start); 482 printf("\t%s", time_buf); 483 } 484