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