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