1 /* $NetBSD: ac.c,v 1.20 2004/04/02 09:58:33 jmmv 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.20 2004/04/02 09:58:33 jmmv 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 <time.h> 65 #include <utmp.h> 66 #include <ttyent.h> 67 68 /* 69 * this is for our list of currently logged in sessions 70 */ 71 struct utmp_list { 72 struct utmp_list *next; 73 struct utmp usr; 74 }; 75 76 /* 77 * this is for our list of users that are accumulating time. 78 */ 79 struct user_list { 80 struct user_list *next; 81 char name[UT_NAMESIZE+1]; 82 time_t secs; 83 }; 84 85 /* 86 * this is for chosing whether to ignore a login 87 */ 88 struct tty_list { 89 struct tty_list *next; 90 char name[UT_LINESIZE+3]; 91 int len; 92 int ret; 93 }; 94 95 /* 96 * globals - yes yuk 97 */ 98 static time_t Total = 0; 99 static time_t FirstTime = 0; 100 static int Flags = 0; 101 static struct user_list *Users = NULL; 102 static struct tty_list *Ttys = NULL; 103 static int Maxcon = 0, Ncon = 0; 104 static char (*Con)[UT_LINESIZE] = NULL; 105 106 #define NEW(type) (type *)malloc(sizeof (type)) 107 108 #define is_login_tty(line) \ 109 (bsearch(line, Con, Ncon, sizeof(Con[0]), compare) != NULL) 110 111 #define AC_W 1 /* not _PATH_WTMP */ 112 #define AC_D 2 /* daily totals (ignore -p) */ 113 #define AC_P 4 /* per-user totals */ 114 #define AC_U 8 /* specified users only */ 115 #define AC_T 16 /* specified ttys only */ 116 117 #ifdef DEBUG 118 static int Debug = 0; 119 #endif 120 121 int main __P((int, char **)); 122 static int ac __P((FILE *)); 123 static struct tty_list *add_tty __P((char *)); 124 static int do_tty __P((char *)); 125 static FILE *file __P((char *)); 126 static struct utmp_list *log_in __P((struct utmp_list *, struct utmp *)); 127 static struct utmp_list *log_out __P((struct utmp_list *, struct utmp *)); 128 #ifdef notdef 129 static int on_console __P((struct utmp_list *)); 130 #endif 131 static void find_login_ttys __P((void)); 132 static void show __P((char *, time_t)); 133 static void show_today __P((struct user_list *, struct utmp_list *, 134 time_t)); 135 static void show_users __P((struct user_list *)); 136 static struct user_list *update_user __P((struct user_list *, char *, time_t)); 137 static int compare __P((const void *, const void *)); 138 static void usage __P((void)); 139 140 /* 141 * open wtmp or die 142 */ 143 static FILE * 144 file(name) 145 char *name; 146 { 147 FILE *fp; 148 149 if (strcmp(name, "-") == 0) 150 fp = stdin; 151 else if ((fp = fopen(name, "r")) == NULL) 152 err(1, "%s", name); 153 /* in case we want to discriminate */ 154 if (strcmp(_PATH_WTMP, name)) 155 Flags |= AC_W; 156 return fp; 157 } 158 159 static struct tty_list * 160 add_tty(name) 161 char *name; 162 { 163 struct tty_list *tp; 164 char *rcp; 165 166 Flags |= AC_T; 167 168 if ((tp = NEW(struct tty_list)) == NULL) 169 err(1, "malloc"); 170 tp->len = 0; /* full match */ 171 tp->ret = 1; /* do if match */ 172 if (*name == '!') { /* don't do if match */ 173 tp->ret = 0; 174 name++; 175 } 176 (void)strlcpy(tp->name, name, sizeof (tp->name)); 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 char (*nCon)[UT_LINESIZE]; 227 228 if ((Con = malloc((Maxcon = 10) * sizeof(Con[0]))) == NULL) 229 err(1, "malloc"); 230 231 setttyent(); 232 while ((tty = getttyent()) != NULL) 233 if ((tty->ty_status & TTY_ON) != 0 && 234 strstr(tty->ty_getty, "getty") != NULL) { 235 if (Ncon == Maxcon) { 236 if ((nCon = realloc(Con, (Maxcon + 10) * 237 sizeof(Con[0]))) == NULL) 238 err(1, "malloc"); 239 Con = nCon; 240 Maxcon += 10; 241 } 242 (void)strncpy(Con[Ncon++], tty->ty_name, UT_LINESIZE); 243 } 244 endttyent(); 245 qsort(Con, Ncon, sizeof(Con[0]), compare); 246 } 247 248 #ifdef notdef 249 /* 250 * is someone logged in on Console/login tty? 251 */ 252 static int 253 on_console(head) 254 struct utmp_list *head; 255 { 256 struct utmp_list *up; 257 258 for (up = head; up; up = up->next) 259 if (is_login_tty(up->usr.ut_line)) 260 return 1; 261 262 return 0; 263 } 264 #endif 265 266 /* 267 * update user's login time 268 */ 269 static struct user_list * 270 update_user(head, name, secs) 271 struct user_list *head; 272 char *name; 273 time_t secs; 274 { 275 struct user_list *up; 276 277 for (up = head; up != NULL; up = up->next) { 278 if (strncmp(up->name, name, sizeof (up->name) - 1) == 0) { 279 up->secs += secs; 280 Total += secs; 281 return head; 282 } 283 } 284 /* 285 * not found so add new user unless specified users only 286 */ 287 if (Flags & AC_U) 288 return head; 289 290 if ((up = NEW(struct user_list)) == NULL) 291 err(1, "malloc"); 292 up->next = head; 293 (void)strlcpy(up->name, name, sizeof (up->name)); 294 up->secs = secs; 295 Total += secs; 296 return up; 297 } 298 299 int 300 main(argc, argv) 301 int argc; 302 char **argv; 303 { 304 FILE *fp; 305 int c; 306 307 fp = NULL; 308 while ((c = getopt(argc, argv, "Ddpt:w:")) != -1) { 309 switch (c) { 310 #ifdef DEBUG 311 case 'D': 312 Debug++; 313 break; 314 #endif 315 case 'd': 316 Flags |= AC_D; 317 break; 318 case 'p': 319 Flags |= AC_P; 320 break; 321 case 't': /* only do specified ttys */ 322 add_tty(optarg); 323 break; 324 case 'w': 325 fp = file(optarg); 326 break; 327 case '?': 328 default: 329 usage(); 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