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