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$"); 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 */ 35 struct 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 */ 43 struct 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 */ 52 struct 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 63 static char *Console = CONSOLE_TTY; 64 #endif 65 static time_t Total = 0; 66 static time_t FirstTime = 0; 67 static int Flags = 0; 68 static struct user_list *Users = NULL; 69 static 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 80 static int Debug = 0; 81 #endif 82 83 int main(int, char **); 84 int ac(const char *); 85 struct tty_list *add_tty(char *); 86 #ifdef DEBUG 87 const char *debug_pfx(const struct utmpx *, const struct utmpx *); 88 #endif 89 int do_tty(char *); 90 struct utmp_list *log_in(struct utmp_list *, struct utmpx *); 91 struct utmp_list *log_out(struct utmp_list *, struct utmpx *); 92 int on_console(struct utmp_list *); 93 void show(const char *, time_t); 94 void show_today(struct user_list *, struct utmp_list *, 95 time_t); 96 void show_users(struct user_list *); 97 struct user_list *update_user(struct user_list *, char *, time_t); 98 void usage(void); 99 100 struct tty_list * 101 add_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 */ 129 int 130 do_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 */ 153 int 154 on_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 */ 169 struct user_list * 170 update_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 */ 201 const char * 202 debug_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 256 int 257 main(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 */ 316 void 317 show(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 324 void 325 show_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 */ 336 void 337 show_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 */ 373 struct utmp_list * 374 log_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 */ 413 struct utmp_list * 414 log_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 476 int 477 ac(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 626 void 627 usage(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