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