ac.c revision 216227
1145519Sdarrenr/* 2145510Sdarrenr * Copyright (c) 1994 Christopher G. Demetriou. 322514Sdarrenr * @(#)Copyright (c) 1994, Simon J. Gerraty. 4255332Scy * 522514Sdarrenr * This is free software. It comes with NO WARRANTY. 680486Sdarrenr * Permission to use, modify and distribute this source code 7145510Sdarrenr * is granted subject to the following conditions. 822514Sdarrenr * 1/ that the above copyright notice and this notice 9145510Sdarrenr * are preserved in all copies and that due credit be given 10145510Sdarrenr * to the author. 11255332Scy * 2/ that any changes to this code are clearly commented 1292686Sdarrenr * as such so that the author does not get blamed for bugs 1392686Sdarrenr * other than his own. 1422514Sdarrenr */ 1522514Sdarrenr 1622514Sdarrenr#include <sys/cdefs.h> 1722514Sdarrenr__FBSDID("$FreeBSD: head/usr.sbin/ac/ac.c 216227 2010-12-06 10:24:06Z kevlo $"); 1822514Sdarrenr 1922514Sdarrenr#include <sys/types.h> 20145510Sdarrenr#include <sys/time.h> 21145510Sdarrenr#include <err.h> 22145510Sdarrenr#include <errno.h> 2331183Speter#include <langinfo.h> 2431183Speter#include <locale.h> 2531183Speter#include <stdio.h> 2631183Speter#include <stdlib.h> 2731183Speter#include <string.h> 2831183Speter#include <timeconv.h> 2922514Sdarrenr#include <unistd.h> 30145510Sdarrenr#include <utmpx.h> 31145510Sdarrenr 32145510Sdarrenr/* 33145510Sdarrenr * this is for our list of currently logged in sessions 34145510Sdarrenr */ 35145510Sdarrenrstruct utmp_list { 36145510Sdarrenr struct utmp_list *next; 3724583Sdarrenr struct utmpx usr; 3824583Sdarrenr}; 3924583Sdarrenr 4022514Sdarrenr/* 4122514Sdarrenr * this is for our list of users that are accumulating time. 4222514Sdarrenr */ 4331183Speterstruct user_list { 4422514Sdarrenr struct user_list *next; 4522514Sdarrenr char name[sizeof(((struct utmpx *)0)->ut_user)]; 4622514Sdarrenr time_t secs; 4722514Sdarrenr}; 4822514Sdarrenr 4922514Sdarrenr/* 5022514Sdarrenr * this is for chosing whether to ignore a login 5122514Sdarrenr */ 5222514Sdarrenrstruct tty_list { 5322514Sdarrenr struct tty_list *next; 5422514Sdarrenr char name[sizeof(((struct utmpx *)0)->ut_host) + 2]; 5522514Sdarrenr size_t len; 5622514Sdarrenr int ret; 5722514Sdarrenr}; 5822514Sdarrenr 5922514Sdarrenr/* 6022514Sdarrenr * globals - yes yuk 6122514Sdarrenr */ 6222514Sdarrenr#ifdef CONSOLE_TTY 6322514Sdarrenrstatic char *Console = CONSOLE_TTY; 64145510Sdarrenr#endif 65255332Scystatic time_t Total = 0; 6622514Sdarrenrstatic time_t FirstTime = 0; 6722514Sdarrenrstatic int Flags = 0; 6822514Sdarrenrstatic struct user_list *Users = NULL; 6922514Sdarrenrstatic struct tty_list *Ttys = NULL; 7022514Sdarrenr 7122514Sdarrenr#define NEW(type) (type *)malloc(sizeof (type)) 7222514Sdarrenr 7322514Sdarrenr#define AC_W 1 /* not _PATH_WTMP */ 7422514Sdarrenr#define AC_D 2 /* daily totals (ignore -p) */ 7522514Sdarrenr#define AC_P 4 /* per-user totals */ 7622514Sdarrenr#define AC_U 8 /* specified users only */ 7722514Sdarrenr#define AC_T 16 /* specified ttys only */ 7822514Sdarrenr 7922514Sdarrenr#ifdef DEBUG 8022514Sdarrenrstatic int Debug = 0; 8131183Speter#endif 82255332Scy 83255332Scyint main(int, char **); 84255332Scyint ac(const char *); 85255332Scystruct tty_list *add_tty(char *); 8631183Speter#ifdef DEBUG 8731183Speterconst char *debug_pfx(const struct utmpx *, const struct utmpx *); 8831183Speter#endif 8931183Speterint do_tty(char *); 9031183Speterstruct utmp_list *log_in(struct utmp_list *, struct utmpx *); 9131183Speterstruct utmp_list *log_out(struct utmp_list *, struct utmpx *); 9231183Speterint on_console(struct utmp_list *); 9331183Spetervoid show(const char *, time_t); 9431183Spetervoid show_today(struct user_list *, struct utmp_list *, 9531183Speter time_t); 9631183Spetervoid show_users(struct user_list *); 9731183Speterstruct user_list *update_user(struct user_list *, char *, time_t); 9831183Spetervoid usage(void); 9931183Speter 10031183Speterstruct tty_list * 10131183Speteradd_tty(char *name) 10231183Speter{ 10331183Speter struct tty_list *tp; 10431183Speter char *rcp; 10531183Speter 10631183Speter Flags |= AC_T; 10731183Speter 10831183Speter if ((tp = NEW(struct tty_list)) == NULL) 109130887Sdarrenr errx(1, "malloc failed"); 110130887Sdarrenr tp->len = 0; /* full match */ 111130887Sdarrenr tp->ret = 1; /* do if match */ 112130887Sdarrenr if (*name == '!') { /* don't do if match */ 11331183Speter tp->ret = 0; 11431183Speter name++; 11531183Speter } 11631183Speter strlcpy(tp->name, name, sizeof (tp->name)); 11731183Speter if ((rcp = strchr(tp->name, '*')) != NULL) { /* wild card */ 11831183Speter *rcp = '\0'; 119145510Sdarrenr tp->len = strlen(tp->name); /* match len bytes only */ 12031183Speter } 12131183Speter tp->next = Ttys; 12231183Speter Ttys = tp; 12331183Speter return Ttys; 12431183Speter} 12531183Speter 12631183Speter/* 12731183Speter * should we process the named tty? 12831183Speter */ 12931183Speterint 13031183Speterdo_tty(char *name) 13131183Speter{ 13231183Speter struct tty_list *tp; 13331183Speter int def_ret = 0; 13431183Speter 13531183Speter for (tp = Ttys; tp != NULL; tp = tp->next) { 13631183Speter if (tp->ret == 0) /* specific don't */ 13731183Speter def_ret = 1; /* default do */ 13831183Speter if (tp->len != 0) { 13931183Speter if (strncmp(name, tp->name, tp->len) == 0) 14031183Speter return tp->ret; 14131183Speter } else { 14231183Speter if (strncmp(name, tp->name, sizeof (tp->name)) == 0) 14331183Speter return tp->ret; 14431183Speter } 14531183Speter } 14631183Speter return def_ret; 14731183Speter} 14831183Speter 14931183Speter#ifdef CONSOLE_TTY 15031183Speter/* 15131183Speter * is someone logged in on Console? 15231183Speter */ 153255332Scyint 154255332Scyon_console(struct utmp_list *head) 15522514Sdarrenr{ 15622514Sdarrenr struct utmp_list *up; 15731183Speter 15822514Sdarrenr for (up = head; up; up = up->next) { 15931183Speter if (strcmp(up->usr.ut_line, Console) == 0) 16022514Sdarrenr return 1; 16122514Sdarrenr } 16222514Sdarrenr return 0; 16322514Sdarrenr} 16422514Sdarrenr#endif 16522514Sdarrenr 16622514Sdarrenr/* 16731183Speter * update user's login time 16831183Speter */ 16931183Speterstruct user_list * 17031183Speterupdate_user(struct user_list *head, char *name, time_t secs) 17122514Sdarrenr{ 17222514Sdarrenr struct user_list *up; 17322514Sdarrenr 17422514Sdarrenr for (up = head; up != NULL; up = up->next) { 17522514Sdarrenr if (strcmp(up->name, name) == 0) { 17622514Sdarrenr up->secs += secs; 17722514Sdarrenr Total += secs; 17822514Sdarrenr return head; 17922514Sdarrenr } 18031183Speter } 18131183Speter /* 18231183Speter * not found so add new user unless specified users only 18331183Speter */ 18431183Speter if (Flags & AC_U) 18531183Speter return head; 18631183Speter 18731183Speter if ((up = NEW(struct user_list)) == NULL) 18831183Speter errx(1, "malloc failed"); 18931183Speter up->next = head; 19031183Speter strlcpy(up->name, name, sizeof (up->name)); 19131183Speter up->secs = secs; 19231183Speter Total += secs; 19331183Speter return up; 19431183Speter} 19531183Speter 19631183Speter#ifdef DEBUG 19731183Speter/* 19831183Speter * Create a string which is the standard prefix for a debug line. It 19922514Sdarrenr * includes a timestamp (perhaps with year), device-name, and user-name. 20022514Sdarrenr */ 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/* 411 * if do_tty says ok, login a user 412 */ 413struct utmp_list * 414log_in(struct utmp_list *head, struct utmpx *up) 415{ 416 struct utmp_list *lp; 417 418 /* 419 * this could be a login. if we're not dealing with 420 * the console name, say it is. 421 * 422 * If we are, and if ut_host==":0.0" we know that it 423 * isn't a real login. _But_ if we have not yet recorded 424 * someone being logged in on Console - due to the wtmp 425 * file starting after they logged in, we'll pretend they 426 * logged in, at the start of the wtmp file. 427 */ 428 429#ifdef CONSOLE_TTY 430 if (up->ut_host[0] == ':') { 431 /* 432 * SunOS 4.0.2 does not treat ":0.0" as special but we 433 * do. 434 */ 435 if (on_console(head)) 436 return head; 437 /* 438 * ok, no recorded login, so they were here when wtmp 439 * started! Adjust ut_time! 440 */ 441 up->ut_time = FirstTime; 442 /* 443 * this allows us to pick the right logout 444 */ 445 strlcpy(up->ut_line, Console, sizeof (up->ut_line)); 446 } 447#endif 448 /* 449 * If we are doing specified ttys only, we ignore 450 * anything else. 451 */ 452 if (Flags & AC_T) 453 if (!do_tty(up->ut_line)) 454 return head; 455 456 /* 457 * go ahead and log them in 458 */ 459 if ((lp = NEW(struct utmp_list)) == NULL) 460 errx(1, "malloc failed"); 461 lp->next = head; 462 head = lp; 463 memmove(&lp->usr, up, sizeof *up); 464#ifdef DEBUG 465 if (Debug) { 466 printf("%s logged in", debug_pfx(&lp->usr, up)); 467 if (*up->ut_host) 468 printf(" (%-.*s)", (int)sizeof(up->ut_host), 469 up->ut_host); 470 putchar('\n'); 471 } 472#endif 473 return head; 474} 475 476int 477ac(const char *file) 478{ 479 struct utmp_list *lp, *head = NULL; 480 struct utmpx *usr, usht; 481 struct tm *ltm; 482 time_t prev_secs, secs, ut_timecopy; 483 int day, rfound, tchanged, tskipped; 484 485 day = -1; 486 prev_secs = 1; /* Minimum acceptable date == 1970 */ 487 rfound = tchanged = tskipped = 0; 488 secs = 0; 489 if (setutxdb(UTXDB_LOG, file) != 0) 490 err(1, "%s", file); 491 while ((usr = getutxent()) != NULL) { 492 rfound++; 493 ut_timecopy = usr->ut_tv.tv_sec; 494 /* 495 * With sparc64 using 64-bit time_t's, there is some system 496 * routine which sets ut_time==0 (the high-order word of a 497 * 64-bit time) instead of a 32-bit time value. For those 498 * wtmp files, it is "more-accurate" to substitute the most- 499 * recent time found, instead of throwing away the entire 500 * record. While it is still just a guess, it is a better 501 * guess than throwing away a log-off record and therefore 502 * counting a session as if it continued to the end of the 503 * month, or the next system-reboot. 504 */ 505 if (ut_timecopy == 0 && prev_secs > 1) { 506#ifdef DEBUG 507 if (Debug) 508 printf("%s - date changed to: %s", 509 debug_pfx(usr, usr), ctime(&prev_secs)); 510#endif 511 tchanged++; 512 usr->ut_tv.tv_sec = ut_timecopy = prev_secs; 513 } 514 /* 515 * Skip records where the time goes backwards. 516 */ 517 if (ut_timecopy < prev_secs) { 518#ifdef DEBUG 519 if (Debug) 520 printf("%s - bad date, record skipped\n", 521 debug_pfx(usr, usr)); 522#endif 523 tskipped++; 524 continue; /* Skip this invalid record. */ 525 } 526 prev_secs = ut_timecopy; 527 528 if (!FirstTime) 529 FirstTime = ut_timecopy; 530 if (Flags & AC_D) { 531 ltm = localtime(&ut_timecopy); 532 if (day >= 0 && day != ltm->tm_yday) { 533 day = ltm->tm_yday; 534 /* 535 * print yesterday's total 536 */ 537 secs = ut_timecopy; 538 secs -= ltm->tm_sec; 539 secs -= 60 * ltm->tm_min; 540 secs -= 3600 * ltm->tm_hour; 541 show_today(Users, head, secs); 542 } else 543 day = ltm->tm_yday; 544 } 545 switch(usr->ut_type) { 546 case OLD_TIME: 547 secs = ut_timecopy; 548 break; 549 case NEW_TIME: 550 secs -= ut_timecopy; 551 /* 552 * adjust time for those logged in 553 */ 554 for (lp = head; lp != NULL; lp = lp->next) 555 lp->usr.ut_tv.tv_sec -= secs; 556 break; 557 case BOOT_TIME: 558 case SHUTDOWN_TIME: 559 head = log_out(head, usr); 560 FirstTime = ut_timecopy; /* shouldn't be needed */ 561 break; 562 case USER_PROCESS: 563 /* 564 * if they came in on tty[p-sP-S]*, then it is only 565 * a login session if the ut_host field is non-empty 566 */ 567 if (strncmp(usr->ut_line, "tty", 3) != 0 || 568 strchr("pqrsPQRS", usr->ut_line[3]) == NULL || 569 *usr->ut_host != '\0') 570 head = log_in(head, usr); 571#ifdef DEBUG 572 else if (Debug > 1) 573 /* Things such as 'screen' sessions. */ 574 printf("%s - record ignored\n", 575 debug_pfx(usr, usr)); 576#endif 577 break; 578 case DEAD_PROCESS: 579 head = log_out(head, usr); 580 break; 581 } 582 } 583 endutxent(); 584 if (!(Flags & AC_W)) 585 usht.ut_tv.tv_sec = time(NULL); 586 else 587 usht.ut_tv.tv_sec = ut_timecopy; 588 usht.ut_type = SHUTDOWN_TIME; 589 590 if (Flags & AC_D) { 591 ltm = localtime(&ut_timecopy); 592 if (day >= 0 && day != ltm->tm_yday) { 593 /* 594 * print yesterday's total 595 */ 596 secs = ut_timecopy; 597 secs -= ltm->tm_sec; 598 secs -= 60 * ltm->tm_min; 599 secs -= 3600 * ltm->tm_hour; 600 show_today(Users, head, secs); 601 } 602 } 603 /* 604 * anyone still logged in gets time up to now 605 */ 606 head = log_out(head, &usht); 607 608 if (Flags & AC_D) 609 show_today(Users, head, time((time_t *)0)); 610 else { 611 if (Flags & AC_P) 612 show_users(Users); 613 show("total", Total); 614 } 615 616 if (tskipped > 0) 617 printf("(Skipped %d of %d records due to invalid time values)\n", 618 tskipped, rfound); 619 if (tchanged > 0) 620 printf("(Changed %d of %d records to have a more likely time value)\n", 621 tchanged, rfound); 622 623 return 0; 624} 625 626void 627usage(void) 628{ 629 (void)fprintf(stderr, 630#ifdef CONSOLE_TTY 631 "ac [-dp] [-c console] [-t tty] [-w wtmp] [users ...]\n"); 632#else 633 "ac [-dp] [-t tty] [-w wtmp] [users ...]\n"); 634#endif 635 exit(1); 636} 637