1242891Sed/*- 2242891Sed * Copyright (c) 1994 Christopher G. Demetriou 3242891Sed * Copyright (c) 1994 Simon J. Gerraty 4242891Sed * Copyright (c) 2012 Ed Schouten <ed@FreeBSD.org> 5242891Sed * All rights reserved. 68857Srgrimes * 7242891Sed * Redistribution and use in source and binary forms, with or without 8242891Sed * modification, are permitted provided that the following conditions 9242891Sed * are met: 10242891Sed * 1. Redistributions of source code must retain the above copyright 11242891Sed * notice, this list of conditions and the following disclaimer. 12242891Sed * 2. Redistributions in binary form must reproduce the above copyright 13242891Sed * notice, this list of conditions and the following disclaimer in the 14242891Sed * documentation and/or other materials provided with the distribution. 15242891Sed * 16242891Sed * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17242891Sed * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18242891Sed * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19242891Sed * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20242891Sed * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21242891Sed * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22242891Sed * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23242891Sed * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24242891Sed * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25242891Sed * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26242891Sed * SUCH DAMAGE. 273133Sdg */ 283133Sdg 29105325Scharnier#include <sys/cdefs.h> 30105325Scharnier__FBSDID("$FreeBSD$"); 313133Sdg 32242891Sed#include <sys/queue.h> 333133Sdg#include <sys/time.h> 34242891Sed 353133Sdg#include <err.h> 363133Sdg#include <errno.h> 3774568Sache#include <langinfo.h> 3828995Scharnier#include <locale.h> 393133Sdg#include <stdio.h> 403133Sdg#include <stdlib.h> 413133Sdg#include <string.h> 4299604Sbde#include <timeconv.h> 4328995Scharnier#include <unistd.h> 44202203Sed#include <utmpx.h> 453133Sdg 463133Sdg/* 473133Sdg * this is for our list of currently logged in sessions 483133Sdg */ 49242891Sedstruct utmpx_entry { 50242891Sed SLIST_ENTRY(utmpx_entry) next; 51242891Sed char user[sizeof(((struct utmpx *)0)->ut_user)]; 52242891Sed char id[sizeof(((struct utmpx *)0)->ut_id)]; 53242891Sed#ifdef CONSOLE_TTY 54242891Sed char line[sizeof(((struct utmpx *)0)->ut_line)]; 55242891Sed#endif 56242891Sed struct timeval time; 573133Sdg}; 583133Sdg 593133Sdg/* 603133Sdg * this is for our list of users that are accumulating time. 613133Sdg */ 62242891Sedstruct user_entry { 63242891Sed SLIST_ENTRY(user_entry) next; 64242891Sed char user[sizeof(((struct utmpx *)0)->ut_user)]; 65242891Sed struct timeval time; 663133Sdg}; 673133Sdg 683133Sdg/* 693133Sdg * this is for chosing whether to ignore a login 703133Sdg */ 71242891Sedstruct tty_entry { 72242891Sed SLIST_ENTRY(tty_entry) next; 73242891Sed char line[sizeof(((struct utmpx *)0)->ut_line) + 2]; 74242891Sed size_t len; 75242891Sed int ret; 763133Sdg}; 773133Sdg 783133Sdg/* 793133Sdg * globals - yes yuk 803133Sdg */ 813133Sdg#ifdef CONSOLE_TTY 82242891Sedstatic const char *Console = CONSOLE_TTY; 833133Sdg#endif 84242891Sedstatic struct timeval Total = { 0, 0 }; 85242891Sedstatic struct timeval FirstTime = { 0, 0 }; 863133Sdgstatic int Flags = 0; 87242891Sedstatic SLIST_HEAD(, utmpx_entry) CurUtmpx = SLIST_HEAD_INITIALIZER(CurUtmpx); 88242891Sedstatic SLIST_HEAD(, user_entry) Users = SLIST_HEAD_INITIALIZER(Users); 89242891Sedstatic SLIST_HEAD(, tty_entry) Ttys = SLIST_HEAD_INITIALIZER(Ttys); 903133Sdg 913133Sdg#define AC_W 1 /* not _PATH_WTMP */ 923133Sdg#define AC_D 2 /* daily totals (ignore -p) */ 933133Sdg#define AC_P 4 /* per-user totals */ 943133Sdg#define AC_U 8 /* specified users only */ 953133Sdg#define AC_T 16 /* specified ttys only */ 963133Sdg 97242891Sedstatic void ac(const char *); 98242891Sedstatic void usage(void); 993133Sdg 100242891Sedstatic void 101242891Sedadd_tty(const char *line) 1023133Sdg{ 103242891Sed struct tty_entry *tp; 104126516Sgad char *rcp; 1053133Sdg 1063133Sdg Flags |= AC_T; 1078857Srgrimes 108242891Sed if ((tp = malloc(sizeof(*tp))) == NULL) 10952166Scharnier errx(1, "malloc failed"); 1103133Sdg tp->len = 0; /* full match */ 1113133Sdg tp->ret = 1; /* do if match */ 112242891Sed if (*line == '!') { /* don't do if match */ 1133133Sdg tp->ret = 0; 114242891Sed line++; 1153133Sdg } 116242891Sed strlcpy(tp->line, line, sizeof(tp->line)); 117242891Sed /* Wildcard. */ 118242891Sed if ((rcp = strchr(tp->line, '*')) != NULL) { 1193133Sdg *rcp = '\0'; 120242891Sed /* Match len bytes only. */ 121242891Sed tp->len = strlen(tp->line); 1223133Sdg } 123242891Sed SLIST_INSERT_HEAD(&Ttys, tp, next); 1243133Sdg} 1253133Sdg 1263133Sdg/* 1273133Sdg * should we process the named tty? 1283133Sdg */ 129242891Sedstatic int 130242891Seddo_tty(const char *line) 1313133Sdg{ 132242891Sed struct tty_entry *tp; 1333133Sdg int def_ret = 0; 1348857Srgrimes 135242891Sed SLIST_FOREACH(tp, &Ttys, next) { 1363133Sdg if (tp->ret == 0) /* specific don't */ 1373133Sdg def_ret = 1; /* default do */ 1383133Sdg if (tp->len != 0) { 139242891Sed if (strncmp(line, tp->line, tp->len) == 0) 1403133Sdg return tp->ret; 1413133Sdg } else { 142242891Sed if (strncmp(line, tp->line, sizeof(tp->line)) == 0) 1433133Sdg return tp->ret; 1443133Sdg } 1453133Sdg } 146242891Sed return (def_ret); 1473133Sdg} 1483133Sdg 1493133Sdg#ifdef CONSOLE_TTY 1503133Sdg/* 1513133Sdg * is someone logged in on Console? 1523133Sdg */ 153242891Sedstatic int 154242891Sedon_console(void) 1553133Sdg{ 156242891Sed struct utmpx_entry *up; 1573133Sdg 158242891Sed SLIST_FOREACH(up, &CurUtmpx, next) 159242891Sed if (strcmp(up->line, Console) == 0) 160242891Sed return (1); 161242891Sed return (0); 1623133Sdg} 1633133Sdg#endif 1643133Sdg 1653133Sdg/* 166242891Sed * Update user's login time. 167242891Sed * If no entry for this user is found, a new entry is inserted into the 168242891Sed * list alphabetically. 1693133Sdg */ 170242891Sedstatic void 171242891Sedupdate_user(const char *user, struct timeval secs) 1723133Sdg{ 173242891Sed struct user_entry *up, *aup; 174242891Sed int c; 1753133Sdg 176242891Sed aup = NULL; 177242891Sed SLIST_FOREACH(up, &Users, next) { 178242891Sed c = strcmp(up->user, user); 179242891Sed if (c == 0) { 180242891Sed timeradd(&up->time, &secs, &up->time); 181242891Sed timeradd(&Total, &secs, &Total); 182242891Sed return; 183242891Sed } else if (c > 0) 184242891Sed break; 185242891Sed aup = up; 1863133Sdg } 1873133Sdg /* 1883133Sdg * not found so add new user unless specified users only 1893133Sdg */ 1903133Sdg if (Flags & AC_U) 191242891Sed return; 1928857Srgrimes 193242891Sed if ((up = malloc(sizeof(*up))) == NULL) 19452166Scharnier errx(1, "malloc failed"); 195242891Sed if (aup == NULL) 196242891Sed SLIST_INSERT_HEAD(&Users, up, next); 197242891Sed else 198242891Sed SLIST_INSERT_AFTER(aup, up, next); 199242891Sed strlcpy(up->user, user, sizeof(up->user)); 200242891Sed up->time = secs; 201242891Sed timeradd(&Total, &secs, &Total); 2023133Sdg} 2033133Sdg 2043133Sdgint 205126515Sgadmain(int argc, char *argv[]) 2063133Sdg{ 207202203Sed const char *wtmpf = NULL; 2083133Sdg int c; 2093133Sdg 21011827Sache (void) setlocale(LC_TIME, ""); 21111827Sache 212242891Sed while ((c = getopt(argc, argv, "c:dpt:w:")) != -1) { 2133133Sdg switch (c) { 2143133Sdg case 'c': 2153133Sdg#ifdef CONSOLE_TTY 2163133Sdg Console = optarg; 2173133Sdg#else 2183133Sdg usage(); /* XXX */ 2193133Sdg#endif 2203133Sdg break; 2213133Sdg case 'd': 2223133Sdg Flags |= AC_D; 2233133Sdg break; 2243133Sdg case 'p': 2253133Sdg Flags |= AC_P; 2263133Sdg break; 2273133Sdg case 't': /* only do specified ttys */ 2283133Sdg add_tty(optarg); 2293133Sdg break; 2303133Sdg case 'w': 231202203Sed Flags |= AC_W; 232202203Sed wtmpf = optarg; 2333133Sdg break; 2343133Sdg case '?': 2353133Sdg default: 2363133Sdg usage(); 2373133Sdg break; 2383133Sdg } 2393133Sdg } 2403133Sdg if (optind < argc) { 2413133Sdg /* 2423133Sdg * initialize user list 2433133Sdg */ 2443133Sdg for (; optind < argc; optind++) { 245242891Sed update_user(argv[optind], (struct timeval){ 0, 0 }); 2463133Sdg } 2473133Sdg Flags |= AC_U; /* freeze user list */ 2483133Sdg } 2493133Sdg if (Flags & AC_D) 2503133Sdg Flags &= ~AC_P; 251202203Sed ac(wtmpf); 2528857Srgrimes 253242891Sed return (0); 2543133Sdg} 2553133Sdg 2563133Sdg/* 2573133Sdg * print login time in decimal hours 2583133Sdg */ 259242891Sedstatic void 260242891Sedshow(const char *user, struct timeval secs) 2613133Sdg{ 262202203Sed (void)printf("\t%-*s %8.2f\n", 263242891Sed (int)sizeof(((struct user_entry *)0)->user), user, 264242891Sed (double)secs.tv_sec / 3600); 2653133Sdg} 2663133Sdg 267242891Sedstatic void 268242891Sedshow_users(void) 2693133Sdg{ 270242891Sed struct user_entry *lp; 2713133Sdg 272242891Sed SLIST_FOREACH(lp, &Users, next) 273242891Sed show(lp->user, lp->time); 2743133Sdg} 2753133Sdg 2763133Sdg/* 2773133Sdg * print total login time for 24hr period in decimal hours 2783133Sdg */ 279242891Sedstatic void 280242891Sedshow_today(struct timeval today) 2813133Sdg{ 282242891Sed struct user_entry *up; 283242891Sed struct utmpx_entry *lp; 2843133Sdg char date[64]; 285242891Sed struct timeval diff, total = { 0, 0 }, usec = { 0, 1 }, yesterday; 28674568Sache static int d_first = -1; 2873133Sdg 28874568Sache if (d_first < 0) 28974568Sache d_first = (*nl_langinfo(D_MD_ORDER) == 'd'); 290242891Sed timersub(&today, &usec, &yesterday); 291242891Sed (void)strftime(date, sizeof(date), 29274568Sache d_first ? "%e %b total" : "%b %e total", 293242891Sed localtime(&yesterday.tv_sec)); 2943133Sdg 295242891Sed SLIST_FOREACH(lp, &CurUtmpx, next) { 296242891Sed timersub(&today, &lp->time, &diff); 297242891Sed update_user(lp->user, diff); 298242891Sed /* As if they just logged in. */ 299242891Sed lp->time = today; 3003133Sdg } 301242891Sed SLIST_FOREACH(up, &Users, next) { 302242891Sed timeradd(&total, &up->time, &total); 303242891Sed /* For next day. */ 304242891Sed timerclear(&up->time); 3053133Sdg } 306242891Sed if (timerisset(&total)) 307242891Sed (void)printf("%s %11.2f\n", date, (double)total.tv_sec / 3600); 3083133Sdg} 3093133Sdg 3103133Sdg/* 311242891Sed * Log a user out and update their times. 312242891Sed * If ut_type is BOOT_TIME or SHUTDOWN_TIME, we log all users out as the 313242891Sed * system has been shut down. 3143133Sdg */ 315242891Sedstatic void 316242891Sedlog_out(const struct utmpx *up) 3173133Sdg{ 318242891Sed struct utmpx_entry *lp, *lp2, *tlp; 319242891Sed struct timeval secs; 3208857Srgrimes 321242891Sed for (lp = SLIST_FIRST(&CurUtmpx), lp2 = NULL; lp != NULL;) 322202203Sed if (up->ut_type == BOOT_TIME || up->ut_type == SHUTDOWN_TIME || 323202203Sed (up->ut_type == DEAD_PROCESS && 324242891Sed memcmp(lp->id, up->ut_id, sizeof(up->ut_id)) == 0)) { 325242891Sed timersub(&up->ut_tv, &lp->time, &secs); 326242891Sed update_user(lp->user, secs); 3273133Sdg /* 3283133Sdg * now lose it 3293133Sdg */ 3303133Sdg tlp = lp; 331242891Sed lp = SLIST_NEXT(lp, next); 332242891Sed if (lp2 == NULL) 333242891Sed SLIST_REMOVE_HEAD(&CurUtmpx, next); 334242891Sed else 335242891Sed SLIST_REMOVE_AFTER(lp2, next); 3363133Sdg free(tlp); 3373133Sdg } else { 3383133Sdg lp2 = lp; 339242891Sed lp = SLIST_NEXT(lp, next); 3403133Sdg } 3413133Sdg} 3423133Sdg 3433133Sdg/* 3443133Sdg * if do_tty says ok, login a user 3453133Sdg */ 346242891Sedstatic void 347242891Sedlog_in(struct utmpx *up) 3483133Sdg{ 349242891Sed struct utmpx_entry *lp; 3503133Sdg 3513133Sdg /* 3523133Sdg * this could be a login. if we're not dealing with 3533133Sdg * the console name, say it is. 3543133Sdg * 3553133Sdg * If we are, and if ut_host==":0.0" we know that it 3563133Sdg * isn't a real login. _But_ if we have not yet recorded 3573133Sdg * someone being logged in on Console - due to the wtmp 3583133Sdg * file starting after they logged in, we'll pretend they 3593133Sdg * logged in, at the start of the wtmp file. 3603133Sdg */ 3613133Sdg 3623133Sdg#ifdef CONSOLE_TTY 3633133Sdg if (up->ut_host[0] == ':') { 3643133Sdg /* 3653133Sdg * SunOS 4.0.2 does not treat ":0.0" as special but we 3668857Srgrimes * do. 3673133Sdg */ 368242891Sed if (on_console()) 369242891Sed return; 3703133Sdg /* 3713133Sdg * ok, no recorded login, so they were here when wtmp 3728857Srgrimes * started! Adjust ut_time! 3733133Sdg */ 374242891Sed up->ut_tv = FirstTime; 3753133Sdg /* 3763133Sdg * this allows us to pick the right logout 3773133Sdg */ 378242891Sed strlcpy(up->ut_line, Console, sizeof(up->ut_line)); 3793133Sdg } 3803133Sdg#endif 3813133Sdg /* 3823133Sdg * If we are doing specified ttys only, we ignore 3833133Sdg * anything else. 3843133Sdg */ 385242891Sed if (Flags & AC_T && !do_tty(up->ut_line)) 386242891Sed return; 3873133Sdg 3883133Sdg /* 3893133Sdg * go ahead and log them in 3903133Sdg */ 391242891Sed if ((lp = malloc(sizeof(*lp))) == NULL) 39252166Scharnier errx(1, "malloc failed"); 393242891Sed SLIST_INSERT_HEAD(&CurUtmpx, lp, next); 394242891Sed strlcpy(lp->user, up->ut_user, sizeof(lp->user)); 395242891Sed memcpy(lp->id, up->ut_id, sizeof(lp->id)); 396242891Sed#ifdef CONSOLE_TTY 397242891Sed memcpy(lp->line, up->ut_line, sizeof(lp->line)); 3983133Sdg#endif 399242891Sed lp->time = up->ut_tv; 4003133Sdg} 4013133Sdg 402242891Sedstatic void 403202203Sedac(const char *file) 4043133Sdg{ 405242891Sed struct utmpx_entry *lp; 406202203Sed struct utmpx *usr, usht; 4073133Sdg struct tm *ltm; 408242891Sed struct timeval prev_secs, ut_timecopy, secs, clock_shift, now; 409242891Sed int day, rfound; 4108857Srgrimes 411126752Sgad day = -1; 412242891Sed timerclear(&prev_secs); /* Minimum acceptable date == 1970. */ 413242891Sed timerclear(&secs); 414242891Sed timerclear(&clock_shift); 415242891Sed rfound = 0; 416202203Sed if (setutxdb(UTXDB_LOG, file) != 0) 417202203Sed err(1, "%s", file); 418202203Sed while ((usr = getutxent()) != NULL) { 419126752Sgad rfound++; 420242891Sed ut_timecopy = usr->ut_tv; 421242891Sed /* Don't let the time run backwards. */ 422242891Sed if (timercmp(&ut_timecopy, &prev_secs, <)) 423242891Sed ut_timecopy = prev_secs; 424126752Sgad prev_secs = ut_timecopy; 425126752Sgad 426242891Sed if (!timerisset(&FirstTime)) 427126752Sgad FirstTime = ut_timecopy; 4283133Sdg if (Flags & AC_D) { 429242891Sed ltm = localtime(&ut_timecopy.tv_sec); 4303133Sdg if (day >= 0 && day != ltm->tm_yday) { 4313133Sdg day = ltm->tm_yday; 4323133Sdg /* 4333133Sdg * print yesterday's total 4343133Sdg */ 435126752Sgad secs = ut_timecopy; 436242891Sed secs.tv_sec -= ltm->tm_sec; 437242891Sed secs.tv_sec -= 60 * ltm->tm_min; 438242891Sed secs.tv_sec -= 3600 * ltm->tm_hour; 439242891Sed secs.tv_usec = 0; 440242891Sed show_today(secs); 4413133Sdg } else 4423133Sdg day = ltm->tm_yday; 4433133Sdg } 444202203Sed switch(usr->ut_type) { 445202203Sed case OLD_TIME: 446242891Sed clock_shift = ut_timecopy; 4473133Sdg break; 448202203Sed case NEW_TIME: 449242891Sed timersub(&clock_shift, &ut_timecopy, &clock_shift); 4503133Sdg /* 4513133Sdg * adjust time for those logged in 4523133Sdg */ 453242891Sed SLIST_FOREACH(lp, &CurUtmpx, next) 454242891Sed timersub(&lp->time, &clock_shift, &lp->time); 4553133Sdg break; 456202203Sed case BOOT_TIME: 457202203Sed case SHUTDOWN_TIME: 458242891Sed log_out(usr); 459126752Sgad FirstTime = ut_timecopy; /* shouldn't be needed */ 4603133Sdg break; 461202203Sed case USER_PROCESS: 4623133Sdg /* 463242891Sed * If they came in on pts/..., then it is only 464242891Sed * a login session if the ut_host field is non-empty. 4653133Sdg */ 466242891Sed if (strncmp(usr->ut_line, "pts/", 4) != 0 || 467202203Sed *usr->ut_host != '\0') 468242891Sed log_in(usr); 4693133Sdg break; 470202203Sed case DEAD_PROCESS: 471242891Sed log_out(usr); 472202203Sed break; 4733133Sdg } 4743133Sdg } 475202203Sed endutxent(); 476242891Sed (void)gettimeofday(&now, NULL); 477242891Sed if (Flags & AC_W) 478242891Sed usht.ut_tv = ut_timecopy; 479206095Sed else 480242891Sed usht.ut_tv = now; 481202203Sed usht.ut_type = SHUTDOWN_TIME; 4828857Srgrimes 4833133Sdg if (Flags & AC_D) { 484242891Sed ltm = localtime(&ut_timecopy.tv_sec); 4853133Sdg if (day >= 0 && day != ltm->tm_yday) { 4863133Sdg /* 4873133Sdg * print yesterday's total 4883133Sdg */ 489126752Sgad secs = ut_timecopy; 490242891Sed secs.tv_sec -= ltm->tm_sec; 491242891Sed secs.tv_sec -= 60 * ltm->tm_min; 492242891Sed secs.tv_sec -= 3600 * ltm->tm_hour; 493242891Sed secs.tv_usec = 0; 494242891Sed show_today(secs); 4953133Sdg } 4963133Sdg } 4973133Sdg /* 4983133Sdg * anyone still logged in gets time up to now 4993133Sdg */ 500242891Sed log_out(&usht); 5013133Sdg 5023133Sdg if (Flags & AC_D) 503242891Sed show_today(now); 5043133Sdg else { 5053133Sdg if (Flags & AC_P) 506242891Sed show_users(); 5073133Sdg show("total", Total); 5083133Sdg } 5093133Sdg} 5103133Sdg 511242891Sedstatic void 512126515Sgadusage(void) 5133133Sdg{ 5143133Sdg (void)fprintf(stderr, 5153133Sdg#ifdef CONSOLE_TTY 5163133Sdg "ac [-dp] [-c console] [-t tty] [-w wtmp] [users ...]\n"); 5173133Sdg#else 5183133Sdg "ac [-dp] [-t tty] [-w wtmp] [users ...]\n"); 5193133Sdg#endif 5203133Sdg exit(1); 5213133Sdg} 522