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 53int a_tsize = A_TSIZE; 54int tsize = -1; /* used slots in tbuf table */ 55struct utmpx wb; /* record structure read into */ 56struct ctmp cb; /* record structure written out of */ 57 58struct 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" 70int nsys; 71struct sys { 72 char sname[LSZ]; /* reasons for ACCOUNTING records */ 73 char snum; /* number of times encountered */ 74} sy[NSYS]; 75 76time_t datetime; /* old time if date changed, otherwise 0 */ 77time_t firstime; 78time_t lastime; 79int ndates; /* number of times date changed */ 80int exitcode; 81char *report = NULL; 82char *replin = NULL; 83int printonly; 84int tflag; 85 86static char time_buf[50]; 87uid_t namtouid(); 88dev_t lintodev(); 89static size_t wread(void); 90static int valid(void); 91static void fixup(FILE *); 92static void loop(void); 93static void bootshut(void); 94static int iline(void); 95static void upall(void); 96static void update(struct tbuf *); 97static void printrep(void); 98static void printlin(void); 99static void prctmp(struct ctmp *); 100 101int 102main(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 176static size_t 177wread() 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 */ 186static int 187valid() 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 */ 221static void 222fixup(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 239static void 240loop() 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 */ 289static void 290bootshut() 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 */ 312static int 313iline() 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 334static void 335upall() 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 */ 347static void 348update(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 414static void 415printrep() 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 */ 435static void 436printlin() 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 469static void 470prctmp(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