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