ac.c revision 239855
1/* 2 * Copyright (c) 1994 Christopher G. Demetriou. 3 * @(#)Copyright (c) 1994, Simon J. Gerraty. 4 * 5 * This is free software. It comes with NO WARRANTY. 6 * Permission to use, modify and distribute this source code 7 * is granted subject to the following conditions. 8 * 1/ that the above copyright notice and this notice 9 * are preserved in all copies and that due credit be given 10 * to the author. 11 * 2/ that any changes to this code are clearly commented 12 * as such so that the author does not get blamed for bugs 13 * other than his own. 14 */ 15 16#include <sys/cdefs.h> 17__FBSDID("$FreeBSD: head/usr.sbin/ac/ac.c 239855 2012-08-29 16:15:22Z ed $"); 18 19#include <sys/types.h> 20#include <sys/time.h> 21#include <err.h> 22#include <errno.h> 23#include <langinfo.h> 24#include <locale.h> 25#include <stdio.h> 26#include <stdlib.h> 27#include <string.h> 28#include <timeconv.h> 29#include <unistd.h> 30#include <utmpx.h> 31 32/* 33 * this is for our list of currently logged in sessions 34 */ 35struct utmp_list { 36 struct utmp_list *next; 37 struct utmpx usr; 38}; 39 40/* 41 * this is for our list of users that are accumulating time. 42 */ 43struct user_list { 44 struct user_list *next; 45 char name[sizeof(((struct utmpx *)0)->ut_user)]; 46 time_t secs; 47}; 48 49/* 50 * this is for chosing whether to ignore a login 51 */ 52struct tty_list { 53 struct tty_list *next; 54 char name[sizeof(((struct utmpx *)0)->ut_host) + 2]; 55 size_t len; 56 int ret; 57}; 58 59/* 60 * globals - yes yuk 61 */ 62#ifdef CONSOLE_TTY 63static char *Console = CONSOLE_TTY; 64#endif 65static time_t Total = 0; 66static time_t FirstTime = 0; 67static int Flags = 0; 68static struct user_list *Users = NULL; 69static struct tty_list *Ttys = NULL; 70 71#define NEW(type) (type *)malloc(sizeof (type)) 72 73#define AC_W 1 /* not _PATH_WTMP */ 74#define AC_D 2 /* daily totals (ignore -p) */ 75#define AC_P 4 /* per-user totals */ 76#define AC_U 8 /* specified users only */ 77#define AC_T 16 /* specified ttys only */ 78 79#ifdef DEBUG 80static int Debug = 0; 81#endif 82 83int main(int, char **); 84int ac(const char *); 85struct tty_list *add_tty(char *); 86#ifdef DEBUG 87const char *debug_pfx(const struct utmpx *, const struct utmpx *); 88#endif 89int do_tty(char *); 90struct utmp_list *log_in(struct utmp_list *, struct utmpx *); 91struct utmp_list *log_out(struct utmp_list *, struct utmpx *); 92int on_console(struct utmp_list *); 93void show(const char *, time_t); 94void show_today(struct user_list *, struct utmp_list *, 95 time_t); 96void show_users(struct user_list *); 97struct user_list *update_user(struct user_list *, char *, time_t); 98void usage(void); 99 100struct tty_list * 101add_tty(char *name) 102{ 103 struct tty_list *tp; 104 char *rcp; 105 106 Flags |= AC_T; 107 108 if ((tp = NEW(struct tty_list)) == NULL) 109 errx(1, "malloc failed"); 110 tp->len = 0; /* full match */ 111 tp->ret = 1; /* do if match */ 112 if (*name == '!') { /* don't do if match */ 113 tp->ret = 0; 114 name++; 115 } 116 strlcpy(tp->name, name, sizeof (tp->name)); 117 if ((rcp = strchr(tp->name, '*')) != NULL) { /* wild card */ 118 *rcp = '\0'; 119 tp->len = strlen(tp->name); /* match len bytes only */ 120 } 121 tp->next = Ttys; 122 Ttys = tp; 123 return Ttys; 124} 125 126/* 127 * should we process the named tty? 128 */ 129int 130do_tty(char *name) 131{ 132 struct tty_list *tp; 133 int def_ret = 0; 134 135 for (tp = Ttys; tp != NULL; tp = tp->next) { 136 if (tp->ret == 0) /* specific don't */ 137 def_ret = 1; /* default do */ 138 if (tp->len != 0) { 139 if (strncmp(name, tp->name, tp->len) == 0) 140 return tp->ret; 141 } else { 142 if (strncmp(name, tp->name, sizeof (tp->name)) == 0) 143 return tp->ret; 144 } 145 } 146 return def_ret; 147} 148 149#ifdef CONSOLE_TTY 150/* 151 * is someone logged in on Console? 152 */ 153int 154on_console(struct utmp_list *head) 155{ 156 struct utmp_list *up; 157 158 for (up = head; up; up = up->next) { 159 if (strcmp(up->usr.ut_line, Console) == 0) 160 return 1; 161 } 162 return 0; 163} 164#endif 165 166/* 167 * update user's login time 168 */ 169struct user_list * 170update_user(struct user_list *head, char *name, time_t secs) 171{ 172 struct user_list *up; 173 174 for (up = head; up != NULL; up = up->next) { 175 if (strcmp(up->name, name) == 0) { 176 up->secs += secs; 177 Total += secs; 178 return head; 179 } 180 } 181 /* 182 * not found so add new user unless specified users only 183 */ 184 if (Flags & AC_U) 185 return head; 186 187 if ((up = NEW(struct user_list)) == NULL) 188 errx(1, "malloc failed"); 189 up->next = head; 190 strlcpy(up->name, name, sizeof (up->name)); 191 up->secs = secs; 192 Total += secs; 193 return up; 194} 195 196#ifdef DEBUG 197/* 198 * Create a string which is the standard prefix for a debug line. It 199 * includes a timestamp (perhaps with year), device-name, and user-name. 200 */ 201const char * 202debug_pfx(const struct utmpx *event_up, const struct utmpx *userinf_up) 203{ 204 static char str_result[40 + sizeof(userinf_up->ut_line) + 205 sizeof(userinf_up->ut_user)]; 206 static char thisyear[5]; 207 size_t maxcopy; 208 time_t ut_timecopy; 209 210 if (thisyear[0] == '\0') { 211 /* Figure out what "this year" is. */ 212 time(&ut_timecopy); 213 strlcpy(str_result, ctime(&ut_timecopy), sizeof(str_result)); 214 strlcpy(thisyear, &str_result[20], sizeof(thisyear)); 215 } 216 217 if (event_up->ut_tv.tv_sec == 0) 218 strlcpy(str_result, "*ZeroTime* --:--:-- ", sizeof(str_result)); 219 else { 220 ut_timecopy = event_up->ut_tv.tv_sec; 221 strlcpy(str_result, ctime(&ut_timecopy), sizeof(str_result)); 222 /* 223 * Include the year, if it is not the same year as "now". 224 */ 225 if (strncmp(&str_result[20], thisyear, 4) == 0) 226 str_result[20] = '\0'; 227 else { 228 str_result[24] = ' '; /* Replace a '\n' */ 229 str_result[25] = '\0'; 230 } 231 } 232 233 if (userinf_up->ut_line[0] == '\0') 234 strlcat(str_result, "NoDev", sizeof(str_result)); 235 else { 236 maxcopy = strlen(str_result) + sizeof(userinf_up->ut_line); 237 if (maxcopy > sizeof(str_result)) 238 maxcopy = sizeof(str_result); 239 strlcat(str_result, userinf_up->ut_line, maxcopy); 240 } 241 strlcat(str_result, ": ", sizeof(str_result)); 242 243 if (userinf_up->ut_user[0] == '\0') 244 strlcat(str_result, "LogOff", sizeof(str_result)); 245 else { 246 maxcopy = strlen(str_result) + sizeof(userinf_up->ut_user); 247 if (maxcopy > sizeof(str_result)) 248 maxcopy = sizeof(str_result); 249 strlcat(str_result, userinf_up->ut_user, maxcopy); 250 } 251 252 return (str_result); 253} 254#endif 255 256int 257main(int argc, char *argv[]) 258{ 259 const char *wtmpf = NULL; 260 int c; 261 262 (void) setlocale(LC_TIME, ""); 263 264 while ((c = getopt(argc, argv, "Dc:dpt:w:")) != -1) { 265 switch (c) { 266#ifdef DEBUG 267 case 'D': 268 Debug++; 269 break; 270#endif 271 case 'c': 272#ifdef CONSOLE_TTY 273 Console = optarg; 274#else 275 usage(); /* XXX */ 276#endif 277 break; 278 case 'd': 279 Flags |= AC_D; 280 break; 281 case 'p': 282 Flags |= AC_P; 283 break; 284 case 't': /* only do specified ttys */ 285 add_tty(optarg); 286 break; 287 case 'w': 288 Flags |= AC_W; 289 wtmpf = optarg; 290 break; 291 case '?': 292 default: 293 usage(); 294 break; 295 } 296 } 297 if (optind < argc) { 298 /* 299 * initialize user list 300 */ 301 for (; optind < argc; optind++) { 302 Users = update_user(Users, argv[optind], (time_t)0); 303 } 304 Flags |= AC_U; /* freeze user list */ 305 } 306 if (Flags & AC_D) 307 Flags &= ~AC_P; 308 ac(wtmpf); 309 310 return 0; 311} 312 313/* 314 * print login time in decimal hours 315 */ 316void 317show(const char *name, time_t secs) 318{ 319 (void)printf("\t%-*s %8.2f\n", 320 (int)sizeof(((struct utmpx *)0)->ut_user), name, 321 ((double)secs / 3600)); 322} 323 324void 325show_users(struct user_list *list) 326{ 327 struct user_list *lp; 328 329 for (lp = list; lp; lp = lp->next) 330 show(lp->name, lp->secs); 331} 332 333/* 334 * print total login time for 24hr period in decimal hours 335 */ 336void 337show_today(struct user_list *users, struct utmp_list *logins, time_t secs) 338{ 339 struct user_list *up; 340 struct utmp_list *lp; 341 char date[64]; 342 time_t yesterday = secs - 1; 343 static int d_first = -1; 344 345 if (d_first < 0) 346 d_first = (*nl_langinfo(D_MD_ORDER) == 'd'); 347 (void)strftime(date, sizeof (date), 348 d_first ? "%e %b total" : "%b %e total", 349 localtime(&yesterday)); 350 351 /* restore the missing second */ 352 yesterday++; 353 354 for (lp = logins; lp != NULL; lp = lp->next) { 355 secs = yesterday - lp->usr.ut_tv.tv_sec; 356 Users = update_user(Users, lp->usr.ut_user, secs); 357 lp->usr.ut_tv.tv_sec = yesterday; /* as if they just logged in */ 358 } 359 secs = 0; 360 for (up = users; up != NULL; up = up->next) { 361 secs += up->secs; 362 up->secs = 0; /* for next day */ 363 } 364 if (secs) 365 (void)printf("%s %11.2f\n", date, ((double)secs / 3600)); 366} 367 368/* 369 * log a user out and update their times. 370 * if ut_line is "~", we log all users out as the system has 371 * been shut down. 372 */ 373struct utmp_list * 374log_out(struct utmp_list *head, struct utmpx *up) 375{ 376 struct utmp_list *lp, *lp2, *tlp; 377 time_t secs; 378 379 for (lp = head, lp2 = NULL; lp != NULL; ) 380 if (up->ut_type == BOOT_TIME || up->ut_type == SHUTDOWN_TIME || 381 (up->ut_type == DEAD_PROCESS && 382 memcmp(lp->usr.ut_id, up->ut_id, sizeof up->ut_id) == 0)) { 383 secs = up->ut_tv.tv_sec - lp->usr.ut_tv.tv_sec; 384 Users = update_user(Users, lp->usr.ut_user, secs); 385#ifdef DEBUG 386 if (Debug) 387 printf("%s logged out (%2d:%02d:%02d)\n", 388 debug_pfx(up, &lp->usr), (int)(secs / 3600), 389 (int)((secs % 3600) / 60), 390 (int)(secs % 60)); 391#endif 392 /* 393 * now lose it 394 */ 395 tlp = lp; 396 lp = lp->next; 397 if (tlp == head) 398 head = lp; 399 else if (lp2 != NULL) 400 lp2->next = lp; 401 free(tlp); 402 } else { 403 lp2 = lp; 404 lp = lp->next; 405 } 406 return head; 407} 408 409/* 410 * if do_tty says ok, login a user 411 */ 412struct utmp_list * 413log_in(struct utmp_list *head, struct utmpx *up) 414{ 415 struct utmp_list *lp; 416 417 /* 418 * this could be a login. if we're not dealing with 419 * the console name, say it is. 420 * 421 * If we are, and if ut_host==":0.0" we know that it 422 * isn't a real login. _But_ if we have not yet recorded 423 * someone being logged in on Console - due to the wtmp 424 * file starting after they logged in, we'll pretend they 425 * logged in, at the start of the wtmp file. 426 */ 427 428#ifdef CONSOLE_TTY 429 if (up->ut_host[0] == ':') { 430 /* 431 * SunOS 4.0.2 does not treat ":0.0" as special but we 432 * do. 433 */ 434 if (on_console(head)) 435 return head; 436 /* 437 * ok, no recorded login, so they were here when wtmp 438 * started! Adjust ut_time! 439 */ 440 up->ut_time = FirstTime; 441 /* 442 * this allows us to pick the right logout 443 */ 444 strlcpy(up->ut_line, Console, sizeof (up->ut_line)); 445 } 446#endif 447 /* 448 * If we are doing specified ttys only, we ignore 449 * anything else. 450 */ 451 if (Flags & AC_T) 452 if (!do_tty(up->ut_line)) 453 return head; 454 455 /* 456 * go ahead and log them in 457 */ 458 if ((lp = NEW(struct utmp_list)) == NULL) 459 errx(1, "malloc failed"); 460 lp->next = head; 461 head = lp; 462 memmove(&lp->usr, up, sizeof *up); 463#ifdef DEBUG 464 if (Debug) { 465 printf("%s logged in", debug_pfx(&lp->usr, up)); 466 if (*up->ut_host) 467 printf(" (%-.*s)", (int)sizeof(up->ut_host), 468 up->ut_host); 469 putchar('\n'); 470 } 471#endif 472 return head; 473} 474 475int 476ac(const char *file) 477{ 478 struct utmp_list *lp, *head = NULL; 479 struct utmpx *usr, usht; 480 struct tm *ltm; 481 time_t prev_secs, secs, ut_timecopy; 482 int day, rfound, tchanged, tskipped; 483 484 day = -1; 485 prev_secs = 1; /* Minimum acceptable date == 1970 */ 486 rfound = tchanged = tskipped = 0; 487 secs = 0; 488 if (setutxdb(UTXDB_LOG, file) != 0) 489 err(1, "%s", file); 490 while ((usr = getutxent()) != NULL) { 491 rfound++; 492 ut_timecopy = usr->ut_tv.tv_sec; 493 /* 494 * With sparc64 using 64-bit time_t's, there is some system 495 * routine which sets ut_time==0 (the high-order word of a 496 * 64-bit time) instead of a 32-bit time value. For those 497 * wtmp files, it is "more-accurate" to substitute the most- 498 * recent time found, instead of throwing away the entire 499 * record. While it is still just a guess, it is a better 500 * guess than throwing away a log-off record and therefore 501 * counting a session as if it continued to the end of the 502 * month, or the next system-reboot. 503 */ 504 if (ut_timecopy == 0 && prev_secs > 1) { 505#ifdef DEBUG 506 if (Debug) 507 printf("%s - date changed to: %s", 508 debug_pfx(usr, usr), ctime(&prev_secs)); 509#endif 510 tchanged++; 511 usr->ut_tv.tv_sec = ut_timecopy = prev_secs; 512 } 513 /* 514 * Skip records where the time goes backwards. 515 */ 516 if (ut_timecopy < prev_secs) { 517#ifdef DEBUG 518 if (Debug) 519 printf("%s - bad date, record skipped\n", 520 debug_pfx(usr, usr)); 521#endif 522 tskipped++; 523 continue; /* Skip this invalid record. */ 524 } 525 prev_secs = ut_timecopy; 526 527 if (!FirstTime) 528 FirstTime = ut_timecopy; 529 if (Flags & AC_D) { 530 ltm = localtime(&ut_timecopy); 531 if (day >= 0 && day != ltm->tm_yday) { 532 day = ltm->tm_yday; 533 /* 534 * print yesterday's total 535 */ 536 secs = ut_timecopy; 537 secs -= ltm->tm_sec; 538 secs -= 60 * ltm->tm_min; 539 secs -= 3600 * ltm->tm_hour; 540 show_today(Users, head, secs); 541 } else 542 day = ltm->tm_yday; 543 } 544 switch(usr->ut_type) { 545 case OLD_TIME: 546 secs = ut_timecopy; 547 break; 548 case NEW_TIME: 549 secs -= ut_timecopy; 550 /* 551 * adjust time for those logged in 552 */ 553 for (lp = head; lp != NULL; lp = lp->next) 554 lp->usr.ut_tv.tv_sec -= secs; 555 break; 556 case BOOT_TIME: 557 case SHUTDOWN_TIME: 558 head = log_out(head, usr); 559 FirstTime = ut_timecopy; /* shouldn't be needed */ 560 break; 561 case USER_PROCESS: 562 /* 563 * if they came in on tty[p-sP-S]*, then it is only 564 * a login session if the ut_host field is non-empty 565 */ 566 if (strncmp(usr->ut_line, "tty", 3) != 0 || 567 strchr("pqrsPQRS", usr->ut_line[3]) == NULL || 568 *usr->ut_host != '\0') 569 head = log_in(head, usr); 570#ifdef DEBUG 571 else if (Debug > 1) 572 /* Things such as 'screen' sessions. */ 573 printf("%s - record ignored\n", 574 debug_pfx(usr, usr)); 575#endif 576 break; 577 case DEAD_PROCESS: 578 head = log_out(head, usr); 579 break; 580 } 581 } 582 endutxent(); 583 if (!(Flags & AC_W)) 584 usht.ut_tv.tv_sec = time(NULL); 585 else 586 usht.ut_tv.tv_sec = ut_timecopy; 587 usht.ut_type = SHUTDOWN_TIME; 588 589 if (Flags & AC_D) { 590 ltm = localtime(&ut_timecopy); 591 if (day >= 0 && day != ltm->tm_yday) { 592 /* 593 * print yesterday's total 594 */ 595 secs = ut_timecopy; 596 secs -= ltm->tm_sec; 597 secs -= 60 * ltm->tm_min; 598 secs -= 3600 * ltm->tm_hour; 599 show_today(Users, head, secs); 600 } 601 } 602 /* 603 * anyone still logged in gets time up to now 604 */ 605 head = log_out(head, &usht); 606 607 if (Flags & AC_D) 608 show_today(Users, head, time((time_t *)0)); 609 else { 610 if (Flags & AC_P) 611 show_users(Users); 612 show("total", Total); 613 } 614 615 if (tskipped > 0) 616 printf("(Skipped %d of %d records due to invalid time values)\n", 617 tskipped, rfound); 618 if (tchanged > 0) 619 printf("(Changed %d of %d records to have a more likely time value)\n", 620 tchanged, rfound); 621 622 return 0; 623} 624 625void 626usage(void) 627{ 628 (void)fprintf(stderr, 629#ifdef CONSOLE_TTY 630 "ac [-dp] [-c console] [-t tty] [-w wtmp] [users ...]\n"); 631#else 632 "ac [-dp] [-t tty] [-w wtmp] [users ...]\n"); 633#endif 634 exit(1); 635} 636