1 /* $NetBSD: ac.c,v 1.15 2003/05/17 18:55:18 itojun 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.15 2003/05/17 18:55:18 itojun 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)strlcpy(tp->name, name, sizeof (tp->name)); 178 if ((rcp = strchr(tp->name, '*')) != NULL) { /* wild card */ 179 *rcp = '\0'; 180 tp->len = strlen(tp->name); /* match len bytes only */ 181 } 182 tp->next = Ttys; 183 Ttys = tp; 184 return Ttys; 185 } 186 187 /* 188 * should we process the named tty? 189 */ 190 static int 191 do_tty(name) 192 char *name; 193 { 194 struct tty_list *tp; 195 int def_ret = 0; 196 197 for (tp = Ttys; tp != NULL; tp = tp->next) { 198 if (tp->ret == 0) /* specific don't */ 199 def_ret = 1; /* default do */ 200 if (tp->len != 0) { 201 if (strncmp(name, tp->name, tp->len) == 0) 202 return tp->ret; 203 } else { 204 if (strncmp(name, tp->name, sizeof (tp->name)) == 0) 205 return tp->ret; 206 } 207 } 208 return def_ret; 209 } 210 211 static int 212 compare(a, b) 213 const void *a, *b; 214 { 215 return strncmp(a, b, UT_LINESIZE); 216 } 217 218 /* 219 * Deal correctly with multiple virtual consoles/login ttys. 220 * We read the ttyent's from /etc/ttys and classify as login 221 * ttys ones that are running getty and they are turned on. 222 */ 223 static void 224 find_login_ttys() 225 { 226 struct ttyent *tty; 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 ((Con = realloc(Con, (Maxcon += 10) * 237 sizeof(Con[0]))) == NULL) 238 err(1, "malloc"); 239 (void)strncpy(Con[Ncon++], tty->ty_name, UT_LINESIZE); 240 } 241 endttyent(); 242 qsort(Con, Ncon, sizeof(Con[0]), compare); 243 } 244 245 #ifdef notdef 246 /* 247 * is someone logged in on Console/login tty? 248 */ 249 static int 250 on_console(head) 251 struct utmp_list *head; 252 { 253 struct utmp_list *up; 254 255 for (up = head; up; up = up->next) 256 if (is_login_tty(up->usr.ut_line)) 257 return 1; 258 259 return 0; 260 } 261 #endif 262 263 /* 264 * update user's login time 265 */ 266 static struct user_list * 267 update_user(head, name, secs) 268 struct user_list *head; 269 char *name; 270 time_t secs; 271 { 272 struct user_list *up; 273 274 for (up = head; up != NULL; up = up->next) { 275 if (strncmp(up->name, name, sizeof (up->name) - 1) == 0) { 276 up->secs += secs; 277 Total += secs; 278 return head; 279 } 280 } 281 /* 282 * not found so add new user unless specified users only 283 */ 284 if (Flags & AC_U) 285 return head; 286 287 if ((up = NEW(struct user_list)) == NULL) 288 err(1, "malloc"); 289 up->next = head; 290 (void)strlcpy(up->name, name, sizeof (up->name)); 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 602 (void)fprintf(stderr, 603 "Usage: %s [-d | -p] [-t tty] [-w wtmp] [users ...]\n", 604 getprogname()); 605 exit(1); 606 } 607