1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 1994 Christopher G. Demetriou 5 * Copyright (c) 1994 Simon J. Gerraty 6 * Copyright (c) 2012 Ed Schouten <ed@FreeBSD.org> 7 * All rights reserved. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 * SUCH DAMAGE. 29 */ 30 31 #include <sys/cdefs.h> 32 __FBSDID("$FreeBSD$"); 33 34 #include <sys/queue.h> 35 #include <sys/time.h> 36 37 #include <err.h> 38 #include <errno.h> 39 #include <langinfo.h> 40 #include <locale.h> 41 #include <stdio.h> 42 #include <stdlib.h> 43 #include <string.h> 44 #include <timeconv.h> 45 #include <unistd.h> 46 #include <utmpx.h> 47 48 /* 49 * this is for our list of currently logged in sessions 50 */ 51 struct utmpx_entry { 52 SLIST_ENTRY(utmpx_entry) next; 53 char user[sizeof(((struct utmpx *)0)->ut_user)]; 54 char id[sizeof(((struct utmpx *)0)->ut_id)]; 55 #ifdef CONSOLE_TTY 56 char line[sizeof(((struct utmpx *)0)->ut_line)]; 57 #endif 58 struct timeval time; 59 }; 60 61 /* 62 * this is for our list of users that are accumulating time. 63 */ 64 struct user_entry { 65 SLIST_ENTRY(user_entry) next; 66 char user[sizeof(((struct utmpx *)0)->ut_user)]; 67 struct timeval time; 68 }; 69 70 /* 71 * this is for choosing whether to ignore a login 72 */ 73 struct tty_entry { 74 SLIST_ENTRY(tty_entry) next; 75 char line[sizeof(((struct utmpx *)0)->ut_line) + 2]; 76 size_t len; 77 int ret; 78 }; 79 80 /* 81 * globals - yes yuk 82 */ 83 #ifdef CONSOLE_TTY 84 static const char *Console = CONSOLE_TTY; 85 #endif 86 static struct timeval Total = { 0, 0 }; 87 static struct timeval FirstTime = { 0, 0 }; 88 static int Flags = 0; 89 static SLIST_HEAD(, utmpx_entry) CurUtmpx = SLIST_HEAD_INITIALIZER(CurUtmpx); 90 static SLIST_HEAD(, user_entry) Users = SLIST_HEAD_INITIALIZER(Users); 91 static SLIST_HEAD(, tty_entry) Ttys = SLIST_HEAD_INITIALIZER(Ttys); 92 93 #define AC_W 1 /* not _PATH_WTMP */ 94 #define AC_D 2 /* daily totals (ignore -p) */ 95 #define AC_P 4 /* per-user totals */ 96 #define AC_U 8 /* specified users only */ 97 #define AC_T 16 /* specified ttys only */ 98 99 static void ac(const char *); 100 static void usage(void); 101 102 static void 103 add_tty(const char *line) 104 { 105 struct tty_entry *tp; 106 char *rcp; 107 108 Flags |= AC_T; 109 110 if ((tp = malloc(sizeof(*tp))) == NULL) 111 errx(1, "malloc failed"); 112 tp->len = 0; /* full match */ 113 tp->ret = 1; /* do if match */ 114 if (*line == '!') { /* don't do if match */ 115 tp->ret = 0; 116 line++; 117 } 118 strlcpy(tp->line, line, sizeof(tp->line)); 119 /* Wildcard. */ 120 if ((rcp = strchr(tp->line, '*')) != NULL) { 121 *rcp = '\0'; 122 /* Match len bytes only. */ 123 tp->len = strlen(tp->line); 124 } 125 SLIST_INSERT_HEAD(&Ttys, tp, next); 126 } 127 128 /* 129 * should we process the named tty? 130 */ 131 static int 132 do_tty(const char *line) 133 { 134 struct tty_entry *tp; 135 int def_ret = 0; 136 137 SLIST_FOREACH(tp, &Ttys, next) { 138 if (tp->ret == 0) /* specific don't */ 139 def_ret = 1; /* default do */ 140 if (tp->len != 0) { 141 if (strncmp(line, tp->line, tp->len) == 0) 142 return tp->ret; 143 } else { 144 if (strncmp(line, tp->line, sizeof(tp->line)) == 0) 145 return tp->ret; 146 } 147 } 148 return (def_ret); 149 } 150 151 #ifdef CONSOLE_TTY 152 /* 153 * is someone logged in on Console? 154 */ 155 static int 156 on_console(void) 157 { 158 struct utmpx_entry *up; 159 160 SLIST_FOREACH(up, &CurUtmpx, next) 161 if (strcmp(up->line, Console) == 0) 162 return (1); 163 return (0); 164 } 165 #endif 166 167 /* 168 * Update user's login time. 169 * If no entry for this user is found, a new entry is inserted into the 170 * list alphabetically. 171 */ 172 static void 173 update_user(const char *user, struct timeval secs) 174 { 175 struct user_entry *up, *aup; 176 int c; 177 178 aup = NULL; 179 SLIST_FOREACH(up, &Users, next) { 180 c = strcmp(up->user, user); 181 if (c == 0) { 182 timeradd(&up->time, &secs, &up->time); 183 timeradd(&Total, &secs, &Total); 184 return; 185 } else if (c > 0) 186 break; 187 aup = up; 188 } 189 /* 190 * not found so add new user unless specified users only 191 */ 192 if (Flags & AC_U) 193 return; 194 195 if ((up = malloc(sizeof(*up))) == NULL) 196 errx(1, "malloc failed"); 197 if (aup == NULL) 198 SLIST_INSERT_HEAD(&Users, up, next); 199 else 200 SLIST_INSERT_AFTER(aup, up, next); 201 strlcpy(up->user, user, sizeof(up->user)); 202 up->time = secs; 203 timeradd(&Total, &secs, &Total); 204 } 205 206 int 207 main(int argc, char *argv[]) 208 { 209 const char *wtmpf = NULL; 210 int c; 211 212 (void) setlocale(LC_TIME, ""); 213 214 while ((c = getopt(argc, argv, "c:dpt:w:")) != -1) { 215 switch (c) { 216 case 'c': 217 #ifdef CONSOLE_TTY 218 Console = optarg; 219 #else 220 usage(); /* XXX */ 221 #endif 222 break; 223 case 'd': 224 Flags |= AC_D; 225 break; 226 case 'p': 227 Flags |= AC_P; 228 break; 229 case 't': /* only do specified ttys */ 230 add_tty(optarg); 231 break; 232 case 'w': 233 Flags |= AC_W; 234 wtmpf = optarg; 235 break; 236 case '?': 237 default: 238 usage(); 239 break; 240 } 241 } 242 if (optind < argc) { 243 /* 244 * initialize user list 245 */ 246 for (; optind < argc; optind++) { 247 update_user(argv[optind], (struct timeval){ 0, 0 }); 248 } 249 Flags |= AC_U; /* freeze user list */ 250 } 251 if (Flags & AC_D) 252 Flags &= ~AC_P; 253 ac(wtmpf); 254 255 return (0); 256 } 257 258 /* 259 * print login time in decimal hours 260 */ 261 static void 262 show(const char *user, struct timeval secs) 263 { 264 (void)printf("\t%-*s %8.2f\n", 265 (int)sizeof(((struct user_entry *)0)->user), user, 266 (double)secs.tv_sec / 3600); 267 } 268 269 static void 270 show_users(void) 271 { 272 struct user_entry *lp; 273 274 SLIST_FOREACH(lp, &Users, next) 275 show(lp->user, lp->time); 276 } 277 278 /* 279 * print total login time for 24hr period in decimal hours 280 */ 281 static void 282 show_today(struct timeval today) 283 { 284 struct user_entry *up; 285 struct utmpx_entry *lp; 286 char date[64]; 287 struct timeval diff, total = { 0, 0 }, usec = { 0, 1 }, yesterday; 288 static int d_first = -1; 289 290 if (d_first < 0) 291 d_first = (*nl_langinfo(D_MD_ORDER) == 'd'); 292 timersub(&today, &usec, &yesterday); 293 (void)strftime(date, sizeof(date), 294 d_first ? "%e %b total" : "%b %e total", 295 localtime(&yesterday.tv_sec)); 296 297 SLIST_FOREACH(lp, &CurUtmpx, next) { 298 timersub(&today, &lp->time, &diff); 299 update_user(lp->user, diff); 300 /* As if they just logged in. */ 301 lp->time = today; 302 } 303 SLIST_FOREACH(up, &Users, next) { 304 timeradd(&total, &up->time, &total); 305 /* For next day. */ 306 timerclear(&up->time); 307 } 308 if (timerisset(&total)) 309 (void)printf("%s %11.2f\n", date, (double)total.tv_sec / 3600); 310 } 311 312 /* 313 * Log a user out and update their times. 314 * If ut_type is BOOT_TIME or SHUTDOWN_TIME, we log all users out as the 315 * system has been shut down. 316 */ 317 static void 318 log_out(const struct utmpx *up) 319 { 320 struct utmpx_entry *lp, *lp2, *tlp; 321 struct timeval secs; 322 323 for (lp = SLIST_FIRST(&CurUtmpx), lp2 = NULL; lp != NULL;) 324 if (up->ut_type == BOOT_TIME || up->ut_type == SHUTDOWN_TIME || 325 (up->ut_type == DEAD_PROCESS && 326 memcmp(lp->id, up->ut_id, sizeof(up->ut_id)) == 0)) { 327 timersub(&up->ut_tv, &lp->time, &secs); 328 update_user(lp->user, secs); 329 /* 330 * now lose it 331 */ 332 tlp = lp; 333 lp = SLIST_NEXT(lp, next); 334 if (lp2 == NULL) 335 SLIST_REMOVE_HEAD(&CurUtmpx, next); 336 else 337 SLIST_REMOVE_AFTER(lp2, next); 338 free(tlp); 339 } else { 340 lp2 = lp; 341 lp = SLIST_NEXT(lp, next); 342 } 343 } 344 345 /* 346 * if do_tty says ok, login a user 347 */ 348 static void 349 log_in(struct utmpx *up) 350 { 351 struct utmpx_entry *lp; 352 353 /* 354 * this could be a login. if we're not dealing with 355 * the console name, say it is. 356 * 357 * If we are, and if ut_host==":0.0" we know that it 358 * isn't a real login. _But_ if we have not yet recorded 359 * someone being logged in on Console - due to the wtmp 360 * file starting after they logged in, we'll pretend they 361 * logged in, at the start of the wtmp file. 362 */ 363 364 #ifdef CONSOLE_TTY 365 if (up->ut_host[0] == ':') { 366 /* 367 * SunOS 4.0.2 does not treat ":0.0" as special but we 368 * do. 369 */ 370 if (on_console()) 371 return; 372 /* 373 * ok, no recorded login, so they were here when wtmp 374 * started! Adjust ut_time! 375 */ 376 up->ut_tv = FirstTime; 377 /* 378 * this allows us to pick the right logout 379 */ 380 strlcpy(up->ut_line, Console, sizeof(up->ut_line)); 381 } 382 #endif 383 /* 384 * If we are doing specified ttys only, we ignore 385 * anything else. 386 */ 387 if (Flags & AC_T && !do_tty(up->ut_line)) 388 return; 389 390 /* 391 * go ahead and log them in 392 */ 393 if ((lp = malloc(sizeof(*lp))) == NULL) 394 errx(1, "malloc failed"); 395 SLIST_INSERT_HEAD(&CurUtmpx, lp, next); 396 strlcpy(lp->user, up->ut_user, sizeof(lp->user)); 397 memcpy(lp->id, up->ut_id, sizeof(lp->id)); 398 #ifdef CONSOLE_TTY 399 memcpy(lp->line, up->ut_line, sizeof(lp->line)); 400 #endif 401 lp->time = up->ut_tv; 402 } 403 404 static void 405 ac(const char *file) 406 { 407 struct utmpx_entry *lp; 408 struct utmpx *usr, usht; 409 struct tm *ltm; 410 struct timeval prev_secs, ut_timecopy, secs, clock_shift, now; 411 int day, rfound; 412 413 day = -1; 414 timerclear(&prev_secs); /* Minimum acceptable date == 1970. */ 415 timerclear(&secs); 416 timerclear(&clock_shift); 417 rfound = 0; 418 if (setutxdb(UTXDB_LOG, file) != 0) 419 err(1, "%s", file); 420 while ((usr = getutxent()) != NULL) { 421 rfound++; 422 ut_timecopy = usr->ut_tv; 423 /* Don't let the time run backwards. */ 424 if (timercmp(&ut_timecopy, &prev_secs, <)) 425 ut_timecopy = prev_secs; 426 prev_secs = ut_timecopy; 427 428 if (!timerisset(&FirstTime)) 429 FirstTime = ut_timecopy; 430 if (Flags & AC_D) { 431 ltm = localtime(&ut_timecopy.tv_sec); 432 if (day >= 0 && day != ltm->tm_yday) { 433 day = ltm->tm_yday; 434 /* 435 * print yesterday's total 436 */ 437 secs = ut_timecopy; 438 secs.tv_sec -= ltm->tm_sec; 439 secs.tv_sec -= 60 * ltm->tm_min; 440 secs.tv_sec -= 3600 * ltm->tm_hour; 441 secs.tv_usec = 0; 442 show_today(secs); 443 } else 444 day = ltm->tm_yday; 445 } 446 switch(usr->ut_type) { 447 case OLD_TIME: 448 clock_shift = ut_timecopy; 449 break; 450 case NEW_TIME: 451 timersub(&clock_shift, &ut_timecopy, &clock_shift); 452 /* 453 * adjust time for those logged in 454 */ 455 SLIST_FOREACH(lp, &CurUtmpx, next) 456 timersub(&lp->time, &clock_shift, &lp->time); 457 break; 458 case BOOT_TIME: 459 case SHUTDOWN_TIME: 460 log_out(usr); 461 FirstTime = ut_timecopy; /* shouldn't be needed */ 462 break; 463 case USER_PROCESS: 464 /* 465 * If they came in on pts/..., then it is only 466 * a login session if the ut_host field is non-empty. 467 */ 468 if (strncmp(usr->ut_line, "pts/", 4) != 0 || 469 *usr->ut_host != '\0') 470 log_in(usr); 471 break; 472 case DEAD_PROCESS: 473 log_out(usr); 474 break; 475 } 476 } 477 endutxent(); 478 (void)gettimeofday(&now, NULL); 479 if (Flags & AC_W) 480 usht.ut_tv = ut_timecopy; 481 else 482 usht.ut_tv = now; 483 usht.ut_type = SHUTDOWN_TIME; 484 485 if (Flags & AC_D) { 486 ltm = localtime(&ut_timecopy.tv_sec); 487 if (day >= 0 && day != ltm->tm_yday) { 488 /* 489 * print yesterday's total 490 */ 491 secs = ut_timecopy; 492 secs.tv_sec -= ltm->tm_sec; 493 secs.tv_sec -= 60 * ltm->tm_min; 494 secs.tv_sec -= 3600 * ltm->tm_hour; 495 secs.tv_usec = 0; 496 show_today(secs); 497 } 498 } 499 /* 500 * anyone still logged in gets time up to now 501 */ 502 log_out(&usht); 503 504 if (Flags & AC_D) 505 show_today(now); 506 else { 507 if (Flags & AC_P) 508 show_users(); 509 show("total", Total); 510 } 511 } 512 513 static void 514 usage(void) 515 { 516 (void)fprintf(stderr, 517 #ifdef CONSOLE_TTY 518 "ac [-dp] [-c console] [-t tty] [-w wtmp] [users ...]\n"); 519 #else 520 "ac [-dp] [-t tty] [-w wtmp] [users ...]\n"); 521 #endif 522 exit(1); 523 } 524