1 /* $NetBSD: ac.c,v 1.9 1999/10/11 11:44:59 mrg Exp $ */ 2 3 /* 4 * Copyright (c) 1994 Christopher G. Demetriou. 5 * @(#)Copyright (c) 1994, Simon J. Gerraty. 6 * 7 * This is free software. It comes with NO WARRANTY. 8 * Permission to use, modify and distribute this source code 9 * is granted subject to the following conditions. 10 * 1/ that the above copyright notice and this notice 11 * are preserved in all copies and that due credit be given 12 * to the author. 13 * 2/ that any changes to this code are clearly commented 14 * as such so that the author does not get blamed for bugs 15 * other than his own. 16 */ 17 18 #include <sys/cdefs.h> 19 #ifndef lint 20 __RCSID("$NetBSD: ac.c,v 1.9 1999/10/11 11:44:59 mrg Exp $"); 21 #endif 22 23 #include <sys/types.h> 24 25 #include <err.h> 26 #include <errno.h> 27 #include <pwd.h> 28 #include <stdio.h> 29 #include <string.h> 30 #include <stdlib.h> 31 #include <unistd.h> 32 #include <string.h> 33 #include <time.h> 34 #include <utmp.h> 35 #include <ttyent.h> 36 37 /* 38 * this is for our list of currently logged in sessions 39 */ 40 struct utmp_list { 41 struct utmp_list *next; 42 struct utmp usr; 43 }; 44 45 /* 46 * this is for our list of users that are accumulating time. 47 */ 48 struct user_list { 49 struct user_list *next; 50 char name[UT_NAMESIZE+1]; 51 time_t secs; 52 }; 53 54 /* 55 * this is for chosing whether to ignore a login 56 */ 57 struct tty_list { 58 struct tty_list *next; 59 char name[UT_LINESIZE+3]; 60 int len; 61 int ret; 62 }; 63 64 /* 65 * globals - yes yuk 66 */ 67 static time_t Total = 0; 68 static time_t FirstTime = 0; 69 static int Flags = 0; 70 static struct user_list *Users = NULL; 71 static struct tty_list *Ttys = NULL; 72 static int Maxcon = 0, Ncon = 0; 73 static char (*Con)[UT_LINESIZE] = NULL; 74 75 #define NEW(type) (type *)malloc(sizeof (type)) 76 77 #define is_login_tty(line) \ 78 (bsearch(line, Con, Ncon, sizeof(Con[0]), compare) != NULL) 79 80 #define AC_W 1 /* not _PATH_WTMP */ 81 #define AC_D 2 /* daily totals (ignore -p) */ 82 #define AC_P 4 /* per-user totals */ 83 #define AC_U 8 /* specified users only */ 84 #define AC_T 16 /* specified ttys only */ 85 86 #ifdef DEBUG 87 static int Debug = 0; 88 #endif 89 90 int main __P((int, char **)); 91 static int ac __P((FILE *)); 92 static struct tty_list *add_tty __P((char *)); 93 static int do_tty __P((char *)); 94 static FILE *file __P((char *)); 95 static struct utmp_list *log_in __P((struct utmp_list *, struct utmp *)); 96 static struct utmp_list *log_out __P((struct utmp_list *, struct utmp *)); 97 #ifdef notdef 98 static int on_console __P((struct utmp_list *)); 99 #endif 100 static void find_login_ttys __P((void)); 101 static void show __P((char *, time_t)); 102 static void show_today __P((struct user_list *, struct utmp_list *, 103 time_t)); 104 static void show_users __P((struct user_list *)); 105 static struct user_list *update_user __P((struct user_list *, char *, time_t)); 106 static int compare __P((const void *, const void *)); 107 static void usage __P((void)); 108 109 /* 110 * open wtmp or die 111 */ 112 static FILE * 113 file(name) 114 char *name; 115 { 116 FILE *fp; 117 118 if ((fp = fopen(name, "r")) == NULL) 119 err(1, "%s", name); 120 /* in case we want to discriminate */ 121 if (strcmp(_PATH_WTMP, name)) 122 Flags |= AC_W; 123 return fp; 124 } 125 126 static struct tty_list * 127 add_tty(name) 128 char *name; 129 { 130 struct tty_list *tp; 131 char *rcp; 132 133 Flags |= AC_T; 134 135 if ((tp = NEW(struct tty_list)) == NULL) 136 err(1, "malloc"); 137 tp->len = 0; /* full match */ 138 tp->ret = 1; /* do if match */ 139 if (*name == '!') { /* don't do if match */ 140 tp->ret = 0; 141 name++; 142 } 143 (void)strncpy(tp->name, name, sizeof (tp->name) - 1); 144 tp->name[sizeof (tp->name) - 1] = '\0'; 145 if ((rcp = strchr(tp->name, '*')) != NULL) { /* wild card */ 146 *rcp = '\0'; 147 tp->len = strlen(tp->name); /* match len bytes only */ 148 } 149 tp->next = Ttys; 150 Ttys = tp; 151 return Ttys; 152 } 153 154 /* 155 * should we process the named tty? 156 */ 157 static int 158 do_tty(name) 159 char *name; 160 { 161 struct tty_list *tp; 162 int def_ret = 0; 163 164 for (tp = Ttys; tp != NULL; tp = tp->next) { 165 if (tp->ret == 0) /* specific don't */ 166 def_ret = 1; /* default do */ 167 if (tp->len != 0) { 168 if (strncmp(name, tp->name, tp->len) == 0) 169 return tp->ret; 170 } else { 171 if (strncmp(name, tp->name, sizeof (tp->name)) == 0) 172 return tp->ret; 173 } 174 } 175 return def_ret; 176 } 177 178 static int 179 compare(a, b) 180 const void *a, *b; 181 { 182 return strncmp(a, b, UT_LINESIZE); 183 } 184 185 /* 186 * Deal correctly with multiple virtual consoles/login ttys. 187 * We read the ttyent's from /etc/ttys and classify as login 188 * ttys ones that are running getty and they are turned on. 189 */ 190 static void 191 find_login_ttys() 192 { 193 struct ttyent *tty; 194 195 if ((Con = malloc((Maxcon = 10) * sizeof(Con[0]))) == NULL) 196 err(1, "malloc"); 197 198 setttyent(); 199 while ((tty = getttyent()) != NULL) 200 if ((tty->ty_status & TTY_ON) != 0 && 201 strstr(tty->ty_getty, "getty") != NULL) { 202 if (Ncon == Maxcon) 203 if ((Con = realloc(Con, (Maxcon += 10) * 204 sizeof(Con[0]))) == NULL) 205 err(1, "malloc"); 206 (void)strncpy(Con[Ncon++], tty->ty_name, UT_LINESIZE); 207 } 208 endttyent(); 209 qsort(Con, Ncon, sizeof(Con[0]), compare); 210 } 211 212 #ifdef notdef 213 /* 214 * is someone logged in on Console/login tty? 215 */ 216 static int 217 on_console(head) 218 struct utmp_list *head; 219 { 220 struct utmp_list *up; 221 222 for (up = head; up; up = up->next) 223 if (is_login_tty(up->usr.ut_line)) 224 return 1; 225 226 return 0; 227 } 228 #endif 229 230 /* 231 * update user's login time 232 */ 233 static struct user_list * 234 update_user(head, name, secs) 235 struct user_list *head; 236 char *name; 237 time_t secs; 238 { 239 struct user_list *up; 240 241 for (up = head; up != NULL; up = up->next) { 242 if (strncmp(up->name, name, sizeof (up->name) - 1) == 0) { 243 up->secs += secs; 244 Total += secs; 245 return head; 246 } 247 } 248 /* 249 * not found so add new user unless specified users only 250 */ 251 if (Flags & AC_U) 252 return head; 253 254 if ((up = NEW(struct user_list)) == NULL) 255 err(1, "malloc"); 256 up->next = head; 257 (void)strncpy(up->name, name, sizeof (up->name) - 1); 258 up->name[sizeof (up->name) - 1] = '\0'; /* paranoid! */ 259 up->secs = secs; 260 Total += secs; 261 return up; 262 } 263 264 int 265 main(argc, argv) 266 int argc; 267 char **argv; 268 { 269 FILE *fp; 270 int c; 271 272 fp = NULL; 273 while ((c = getopt(argc, argv, "Dc:dpt:w:")) != -1) { 274 switch (c) { 275 #ifdef DEBUG 276 case 'D': 277 Debug++; 278 break; 279 #endif 280 case 'd': 281 Flags |= AC_D; 282 break; 283 case 'p': 284 Flags |= AC_P; 285 break; 286 case 't': /* only do specified ttys */ 287 add_tty(optarg); 288 break; 289 case 'w': 290 fp = file(optarg); 291 break; 292 case '?': 293 default: 294 usage(); 295 break; 296 } 297 } 298 299 find_login_ttys(); 300 301 if (optind < argc) { 302 /* 303 * initialize user list 304 */ 305 for (; optind < argc; optind++) { 306 Users = update_user(Users, argv[optind], 0L); 307 } 308 Flags |= AC_U; /* freeze user list */ 309 } 310 if (Flags & AC_D) 311 Flags &= ~AC_P; 312 if (fp == NULL) { 313 /* 314 * if _PATH_WTMP does not exist, exit quietly 315 */ 316 if (access(_PATH_WTMP, 0) != 0 && errno == ENOENT) 317 return 0; 318 319 fp = file(_PATH_WTMP); 320 } 321 ac(fp); 322 323 return 0; 324 } 325 326 /* 327 * print login time in decimal hours 328 */ 329 static void 330 show(name, secs) 331 char *name; 332 time_t secs; 333 { 334 (void)printf("\t%-*s %8.2f\n", UT_NAMESIZE, name, 335 ((double)secs / 3600)); 336 } 337 338 static void 339 show_users(list) 340 struct user_list *list; 341 { 342 struct user_list *lp; 343 344 for (lp = list; lp; lp = lp->next) 345 show(lp->name, lp->secs); 346 } 347 348 /* 349 * print total login time for 24hr period in decimal hours 350 */ 351 static void 352 show_today(users, logins, secs) 353 struct user_list *users; 354 struct utmp_list *logins; 355 time_t secs; 356 { 357 struct user_list *up; 358 struct utmp_list *lp; 359 char date[64]; 360 time_t yesterday = secs - 1; 361 362 (void)strftime(date, sizeof (date), "%b %e total", 363 localtime(&yesterday)); 364 365 /* restore the missing second */ 366 yesterday++; 367 368 for (lp = logins; lp != NULL; lp = lp->next) { 369 secs = yesterday - lp->usr.ut_time; 370 Users = update_user(Users, lp->usr.ut_name, secs); 371 lp->usr.ut_time = yesterday; /* as if they just logged in */ 372 } 373 secs = 0; 374 for (up = users; up != NULL; up = up->next) { 375 secs += up->secs; 376 up->secs = 0; /* for next day */ 377 } 378 if (secs) 379 (void)printf("%s %11.2f\n", date, ((double)secs / 3600)); 380 } 381 382 /* 383 * log a user out and update their times. 384 * if ut_line is "~", we log all users out as the system has 385 * been shut down. 386 */ 387 static struct utmp_list * 388 log_out(head, up) 389 struct utmp_list *head; 390 struct utmp *up; 391 { 392 struct utmp_list *lp, *lp2, *tlp; 393 time_t secs; 394 395 for (lp = head, lp2 = NULL; lp != NULL; ) 396 if (*up->ut_line == '~' || strncmp(lp->usr.ut_line, up->ut_line, 397 sizeof (up->ut_line)) == 0) { 398 secs = up->ut_time - lp->usr.ut_time; 399 Users = update_user(Users, lp->usr.ut_name, secs); 400 #ifdef DEBUG 401 if (Debug) 402 printf("%-.*s %-.*s: %-.*s logged out (%2d:%02d:%02d)\n", 403 19, ctime(&up->ut_time), 404 sizeof (lp->usr.ut_line), lp->usr.ut_line, 405 sizeof (lp->usr.ut_name), lp->usr.ut_name, 406 secs / 3600, (secs % 3600) / 60, secs % 60); 407 #endif 408 /* 409 * now lose it 410 */ 411 tlp = lp; 412 lp = lp->next; 413 if (tlp == head) 414 head = lp; 415 else if (lp2 != NULL) 416 lp2->next = lp; 417 free(tlp); 418 } else { 419 lp2 = lp; 420 lp = lp->next; 421 } 422 return head; 423 } 424 425 426 /* 427 * if do_tty says ok, login a user 428 */ 429 struct utmp_list * 430 log_in(head, up) 431 struct utmp_list *head; 432 struct utmp *up; 433 { 434 struct utmp_list *lp; 435 436 /* 437 * If we are doing specified ttys only, we ignore 438 * anything else. 439 */ 440 if (Flags & AC_T) 441 if (!do_tty(up->ut_line)) 442 return head; 443 444 /* 445 * go ahead and log them in 446 */ 447 if ((lp = NEW(struct utmp_list)) == NULL) 448 err(1, "malloc"); 449 lp->next = head; 450 head = lp; 451 memmove((char *)&lp->usr, (char *)up, sizeof (struct utmp)); 452 #ifdef DEBUG 453 if (Debug) { 454 printf("%-.*s %-.*s: %-.*s logged in", 19, 455 ctime(&lp->usr.ut_time), sizeof (up->ut_line), 456 up->ut_line, sizeof (up->ut_name), up->ut_name); 457 if (*up->ut_host) 458 printf(" (%-.*s)", sizeof (up->ut_host), up->ut_host); 459 putchar('\n'); 460 } 461 #endif 462 return head; 463 } 464 465 static int 466 ac(fp) 467 FILE *fp; 468 { 469 struct utmp_list *lp, *head = NULL; 470 struct utmp usr; 471 struct tm *ltm; 472 time_t secs = 0; 473 int day = -1; 474 475 while (fread((char *)&usr, sizeof(usr), 1, fp) == 1) { 476 if (!FirstTime) 477 FirstTime = usr.ut_time; 478 if (Flags & AC_D) { 479 ltm = localtime(&usr.ut_time); 480 if (day >= 0 && day != ltm->tm_yday) { 481 day = ltm->tm_yday; 482 /* 483 * print yesterday's total 484 */ 485 secs = usr.ut_time; 486 secs -= ltm->tm_sec; 487 secs -= 60 * ltm->tm_min; 488 secs -= 3600 * ltm->tm_hour; 489 show_today(Users, head, secs); 490 } else 491 day = ltm->tm_yday; 492 } 493 switch(*usr.ut_line) { 494 case '|': 495 secs = usr.ut_time; 496 break; 497 case '{': 498 secs -= usr.ut_time; 499 /* 500 * adjust time for those logged in 501 */ 502 for (lp = head; lp != NULL; lp = lp->next) 503 lp->usr.ut_time -= secs; 504 break; 505 case '~': /* reboot or shutdown */ 506 head = log_out(head, &usr); 507 FirstTime = usr.ut_time; /* shouldn't be needed */ 508 break; 509 default: 510 /* 511 * if they came in on tty[p-y]*, then it is only 512 * a login session if the ut_host field is non-empty, 513 * or this tty is a login tty [eg. a console] 514 */ 515 if (*usr.ut_name) { 516 if ((strncmp(usr.ut_line, "tty", 3) != 0 && 517 strncmp(usr.ut_line, "dty", 3) != 0) || 518 strchr("pqrstuvwxyzPQRST", usr.ut_line[3]) == 0 || 519 *usr.ut_host != '\0' || 520 is_login_tty(usr.ut_line)) 521 head = log_in(head, &usr); 522 #ifdef DEBUG 523 else if (Debug) 524 printf("%-.*s %-.*s: %-.*s ignored\n", 525 19, ctime(&usr.ut_time), 526 sizeof (usr.ut_line), usr.ut_line, 527 sizeof (usr.ut_name), usr.ut_name); 528 #endif 529 } else 530 head = log_out(head, &usr); 531 break; 532 } 533 } 534 (void)fclose(fp); 535 usr.ut_time = time((time_t *)0); 536 (void)strcpy(usr.ut_line, "~"); 537 538 if (Flags & AC_D) { 539 ltm = localtime(&usr.ut_time); 540 if (day >= 0 && day != ltm->tm_yday) { 541 /* 542 * print yesterday's total 543 */ 544 secs = usr.ut_time; 545 secs -= ltm->tm_sec; 546 secs -= 60 * ltm->tm_min; 547 secs -= 3600 * ltm->tm_hour; 548 show_today(Users, head, secs); 549 } 550 } 551 /* 552 * anyone still logged in gets time up to now 553 */ 554 head = log_out(head, &usr); 555 556 if (Flags & AC_D) 557 show_today(Users, head, time((time_t *)0)); 558 else { 559 if (Flags & AC_P) 560 show_users(Users); 561 show("total", Total); 562 } 563 return 0; 564 } 565 566 static void 567 usage() 568 { 569 extern char *__progname; 570 571 (void)fprintf(stderr, 572 "Usage: %s [-dp] [-t tty] [-w wtmp] [users ...]\n", __progname); 573 exit(1); 574 } 575