1 /* $NetBSD: ac.c,v 1.14 2001/02/19 23:22:40 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.14 2001/02/19 23:22:40 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 (strcmp(name, "-") == 0) 151 fp = stdin; 152 else if ((fp = fopen(name, "r")) == NULL) 153 err(1, "%s", name); 154 /* in case we want to discriminate */ 155 if (strcmp(_PATH_WTMP, name)) 156 Flags |= AC_W; 157 return fp; 158 } 159 160 static struct tty_list * 161 add_tty(name) 162 char *name; 163 { 164 struct tty_list *tp; 165 char *rcp; 166 167 Flags |= AC_T; 168 169 if ((tp = NEW(struct tty_list)) == NULL) 170 err(1, "malloc"); 171 tp->len = 0; /* full match */ 172 tp->ret = 1; /* do if match */ 173 if (*name == '!') { /* don't do if match */ 174 tp->ret = 0; 175 name++; 176 } 177 (void)strncpy(tp->name, name, sizeof (tp->name) - 1); 178 tp->name[sizeof (tp->name) - 1] = '\0'; 179 if ((rcp = strchr(tp->name, '*')) != NULL) { /* wild card */ 180 *rcp = '\0'; 181 tp->len = strlen(tp->name); /* match len bytes only */ 182 } 183 tp->next = Ttys; 184 Ttys = tp; 185 return Ttys; 186 } 187 188 /* 189 * should we process the named tty? 190 */ 191 static int 192 do_tty(name) 193 char *name; 194 { 195 struct tty_list *tp; 196 int def_ret = 0; 197 198 for (tp = Ttys; tp != NULL; tp = tp->next) { 199 if (tp->ret == 0) /* specific don't */ 200 def_ret = 1; /* default do */ 201 if (tp->len != 0) { 202 if (strncmp(name, tp->name, tp->len) == 0) 203 return tp->ret; 204 } else { 205 if (strncmp(name, tp->name, sizeof (tp->name)) == 0) 206 return tp->ret; 207 } 208 } 209 return def_ret; 210 } 211 212 static int 213 compare(a, b) 214 const void *a, *b; 215 { 216 return strncmp(a, b, UT_LINESIZE); 217 } 218 219 /* 220 * Deal correctly with multiple virtual consoles/login ttys. 221 * We read the ttyent's from /etc/ttys and classify as login 222 * ttys ones that are running getty and they are turned on. 223 */ 224 static void 225 find_login_ttys() 226 { 227 struct ttyent *tty; 228 229 if ((Con = malloc((Maxcon = 10) * sizeof(Con[0]))) == NULL) 230 err(1, "malloc"); 231 232 setttyent(); 233 while ((tty = getttyent()) != NULL) 234 if ((tty->ty_status & TTY_ON) != 0 && 235 strstr(tty->ty_getty, "getty") != NULL) { 236 if (Ncon == Maxcon) 237 if ((Con = realloc(Con, (Maxcon += 10) * 238 sizeof(Con[0]))) == NULL) 239 err(1, "malloc"); 240 (void)strncpy(Con[Ncon++], tty->ty_name, UT_LINESIZE); 241 } 242 endttyent(); 243 qsort(Con, Ncon, sizeof(Con[0]), compare); 244 } 245 246 #ifdef notdef 247 /* 248 * is someone logged in on Console/login tty? 249 */ 250 static int 251 on_console(head) 252 struct utmp_list *head; 253 { 254 struct utmp_list *up; 255 256 for (up = head; up; up = up->next) 257 if (is_login_tty(up->usr.ut_line)) 258 return 1; 259 260 return 0; 261 } 262 #endif 263 264 /* 265 * update user's login time 266 */ 267 static struct user_list * 268 update_user(head, name, secs) 269 struct user_list *head; 270 char *name; 271 time_t secs; 272 { 273 struct user_list *up; 274 275 for (up = head; up != NULL; up = up->next) { 276 if (strncmp(up->name, name, sizeof (up->name) - 1) == 0) { 277 up->secs += secs; 278 Total += secs; 279 return head; 280 } 281 } 282 /* 283 * not found so add new user unless specified users only 284 */ 285 if (Flags & AC_U) 286 return head; 287 288 if ((up = NEW(struct user_list)) == NULL) 289 err(1, "malloc"); 290 up->next = head; 291 (void)strncpy(up->name, name, sizeof (up->name) - 1); 292 up->name[sizeof (up->name) - 1] = '\0'; /* paranoid! */ 293 up->secs = secs; 294 Total += secs; 295 return up; 296 } 297 298 int 299 main(argc, argv) 300 int argc; 301 char **argv; 302 { 303 FILE *fp; 304 int c; 305 306 fp = NULL; 307 while ((c = getopt(argc, argv, "Dc:dpt:w:")) != -1) { 308 switch (c) { 309 #ifdef DEBUG 310 case 'D': 311 Debug++; 312 break; 313 #endif 314 case 'd': 315 Flags |= AC_D; 316 break; 317 case 'p': 318 Flags |= AC_P; 319 break; 320 case 't': /* only do specified ttys */ 321 add_tty(optarg); 322 break; 323 case 'w': 324 fp = file(optarg); 325 break; 326 case '?': 327 default: 328 usage(); 329 break; 330 } 331 } 332 333 find_login_ttys(); 334 335 if (optind < argc) { 336 /* 337 * initialize user list 338 */ 339 for (; optind < argc; optind++) { 340 Users = update_user(Users, argv[optind], 0L); 341 } 342 Flags |= AC_U; /* freeze user list */ 343 } 344 if (Flags & AC_D) 345 Flags &= ~AC_P; 346 if (fp == NULL) { 347 /* 348 * if _PATH_WTMP does not exist, exit quietly 349 */ 350 if (access(_PATH_WTMP, 0) != 0 && errno == ENOENT) 351 return 0; 352 353 fp = file(_PATH_WTMP); 354 } 355 ac(fp); 356 357 return 0; 358 } 359 360 /* 361 * print login time in decimal hours 362 */ 363 static void 364 show(name, secs) 365 char *name; 366 time_t secs; 367 { 368 (void)printf("\t%-*s %8.2f\n", UT_NAMESIZE, name, 369 ((double)secs / 3600)); 370 } 371 372 static void 373 show_users(list) 374 struct user_list *list; 375 { 376 struct user_list *lp; 377 378 for (lp = list; lp; lp = lp->next) 379 show(lp->name, lp->secs); 380 } 381 382 /* 383 * print total login time for 24hr period in decimal hours 384 */ 385 static void 386 show_today(users, logins, secs) 387 struct user_list *users; 388 struct utmp_list *logins; 389 time_t secs; 390 { 391 struct user_list *up; 392 struct utmp_list *lp; 393 char date[64]; 394 time_t yesterday = secs - 1; 395 396 (void)strftime(date, sizeof (date), "%b %e total", 397 localtime(&yesterday)); 398 399 /* restore the missing second */ 400 yesterday++; 401 402 for (lp = logins; lp != NULL; lp = lp->next) { 403 secs = yesterday - lp->usr.ut_time; 404 Users = update_user(Users, lp->usr.ut_name, secs); 405 lp->usr.ut_time = yesterday; /* as if they just logged in */ 406 } 407 secs = 0; 408 for (up = users; up != NULL; up = up->next) { 409 secs += up->secs; 410 up->secs = 0; /* for next day */ 411 } 412 if (secs) 413 (void)printf("%s %11.2f\n", date, ((double)secs / 3600)); 414 } 415 416 /* 417 * log a user out and update their times. 418 * if ut_line is "~", we log all users out as the system has 419 * been shut down. 420 */ 421 static struct utmp_list * 422 log_out(head, up) 423 struct utmp_list *head; 424 struct utmp *up; 425 { 426 struct utmp_list *lp, *lp2, *tlp; 427 time_t secs; 428 429 for (lp = head, lp2 = NULL; lp != NULL; ) 430 if (*up->ut_line == '~' || strncmp(lp->usr.ut_line, up->ut_line, 431 sizeof (up->ut_line)) == 0) { 432 secs = up->ut_time - lp->usr.ut_time; 433 Users = update_user(Users, lp->usr.ut_name, secs); 434 #ifdef DEBUG 435 if (Debug) 436 printf("%-.*s %-.*s: %-.*s logged out (%2d:%02d:%02d)\n", 437 19, ctime(&up->ut_time), 438 sizeof (lp->usr.ut_line), lp->usr.ut_line, 439 sizeof (lp->usr.ut_name), lp->usr.ut_name, 440 secs / 3600, (secs % 3600) / 60, secs % 60); 441 #endif 442 /* 443 * now lose it 444 */ 445 tlp = lp; 446 lp = lp->next; 447 if (tlp == head) 448 head = lp; 449 else if (lp2 != NULL) 450 lp2->next = lp; 451 free(tlp); 452 } else { 453 lp2 = lp; 454 lp = lp->next; 455 } 456 return head; 457 } 458 459 460 /* 461 * if do_tty says ok, login a user 462 */ 463 struct utmp_list * 464 log_in(head, up) 465 struct utmp_list *head; 466 struct utmp *up; 467 { 468 struct utmp_list *lp; 469 470 /* 471 * If we are doing specified ttys only, we ignore 472 * anything else. 473 */ 474 if (Flags & AC_T) 475 if (!do_tty(up->ut_line)) 476 return head; 477 478 /* 479 * go ahead and log them in 480 */ 481 if ((lp = NEW(struct utmp_list)) == NULL) 482 err(1, "malloc"); 483 lp->next = head; 484 head = lp; 485 memmove((char *)&lp->usr, (char *)up, sizeof (struct utmp)); 486 #ifdef DEBUG 487 if (Debug) { 488 printf("%-.*s %-.*s: %-.*s logged in", 19, 489 ctime(&lp->usr.ut_time), sizeof (up->ut_line), 490 up->ut_line, sizeof (up->ut_name), up->ut_name); 491 if (*up->ut_host) 492 printf(" (%-.*s)", sizeof (up->ut_host), up->ut_host); 493 putchar('\n'); 494 } 495 #endif 496 return head; 497 } 498 499 static int 500 ac(fp) 501 FILE *fp; 502 { 503 struct utmp_list *lp, *head = NULL; 504 struct utmp usr; 505 struct tm *ltm; 506 time_t secs = 0; 507 int day = -1; 508 509 while (fread((char *)&usr, sizeof(usr), 1, fp) == 1) { 510 if (!FirstTime) 511 FirstTime = usr.ut_time; 512 if (Flags & AC_D) { 513 ltm = localtime(&usr.ut_time); 514 if (day >= 0 && day != ltm->tm_yday) { 515 day = ltm->tm_yday; 516 /* 517 * print yesterday's total 518 */ 519 secs = usr.ut_time; 520 secs -= ltm->tm_sec; 521 secs -= 60 * ltm->tm_min; 522 secs -= 3600 * ltm->tm_hour; 523 show_today(Users, head, secs); 524 } else 525 day = ltm->tm_yday; 526 } 527 switch(*usr.ut_line) { 528 case '|': 529 secs = usr.ut_time; 530 break; 531 case '{': 532 secs -= usr.ut_time; 533 /* 534 * adjust time for those logged in 535 */ 536 for (lp = head; lp != NULL; lp = lp->next) 537 lp->usr.ut_time -= secs; 538 break; 539 case '~': /* reboot or shutdown */ 540 head = log_out(head, &usr); 541 FirstTime = usr.ut_time; /* shouldn't be needed */ 542 break; 543 default: 544 /* 545 * if they came in on tty[p-y]*, then it is only 546 * a login session if the ut_host field is non-empty, 547 * or this tty is a login tty [eg. a console] 548 */ 549 if (*usr.ut_name) { 550 if ((strncmp(usr.ut_line, "tty", 3) != 0 && 551 strncmp(usr.ut_line, "dty", 3) != 0) || 552 strchr("pqrstuvwxyzPQRST", usr.ut_line[3]) == 0 || 553 *usr.ut_host != '\0' || 554 is_login_tty(usr.ut_line)) 555 head = log_in(head, &usr); 556 #ifdef DEBUG 557 else if (Debug) 558 printf("%-.*s %-.*s: %-.*s ignored\n", 559 19, ctime(&usr.ut_time), 560 sizeof (usr.ut_line), usr.ut_line, 561 sizeof (usr.ut_name), usr.ut_name); 562 #endif 563 } else 564 head = log_out(head, &usr); 565 break; 566 } 567 } 568 (void)fclose(fp); 569 usr.ut_time = time((time_t *)0); 570 (void)strcpy(usr.ut_line, "~"); 571 572 if (Flags & AC_D) { 573 ltm = localtime(&usr.ut_time); 574 if (day >= 0 && day != ltm->tm_yday) { 575 /* 576 * print yesterday's total 577 */ 578 secs = usr.ut_time; 579 secs -= ltm->tm_sec; 580 secs -= 60 * ltm->tm_min; 581 secs -= 3600 * ltm->tm_hour; 582 show_today(Users, head, secs); 583 } 584 } 585 /* 586 * anyone still logged in gets time up to now 587 */ 588 head = log_out(head, &usr); 589 590 if (Flags & AC_D) 591 show_today(Users, head, time((time_t *)0)); 592 else { 593 if (Flags & AC_P) 594 show_users(Users); 595 show("total", Total); 596 } 597 return 0; 598 } 599 600 static void 601 usage() 602 { 603 604 (void)fprintf(stderr, 605 "Usage: %s [-d | -p] [-t tty] [-w wtmp] [users ...]\n", 606 getprogname()); 607 exit(1); 608 } 609