1239954Sed/*- 2239954Sed * Copyright (c) 1994 Christopher G. Demetriou 3239954Sed * Copyright (c) 1994 Simon J. Gerraty 4239954Sed * Copyright (c) 2012 Ed Schouten <ed@FreeBSD.org> 5239954Sed * All rights reserved. 68857Srgrimes * 7239954Sed * Redistribution and use in source and binary forms, with or without 8239954Sed * modification, are permitted provided that the following conditions 9239954Sed * are met: 10239954Sed * 1. Redistributions of source code must retain the above copyright 11239954Sed * notice, this list of conditions and the following disclaimer. 12239954Sed * 2. Redistributions in binary form must reproduce the above copyright 13239954Sed * notice, this list of conditions and the following disclaimer in the 14239954Sed * documentation and/or other materials provided with the distribution. 15239954Sed * 16239954Sed * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17239954Sed * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18239954Sed * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19239954Sed * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20239954Sed * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21239954Sed * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22239954Sed * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23239954Sed * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24239954Sed * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25239954Sed * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26239954Sed * SUCH DAMAGE. 273133Sdg */ 283133Sdg 29105325Scharnier#include <sys/cdefs.h> 30105325Scharnier__FBSDID("$FreeBSD$"); 313133Sdg 32239910Sed#include <sys/queue.h> 333133Sdg#include <sys/time.h> 34239910Sed 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 */ 49239910Sedstruct utmpx_entry { 50239910Sed SLIST_ENTRY(utmpx_entry) next; 51239910Sed char user[sizeof(((struct utmpx *)0)->ut_user)]; 52239910Sed char id[sizeof(((struct utmpx *)0)->ut_id)]; 53239910Sed#ifdef CONSOLE_TTY 54239910Sed char line[sizeof(((struct utmpx *)0)->ut_line)]; 55239910Sed#endif 56239910Sed struct timeval time; 573133Sdg}; 583133Sdg 593133Sdg/* 603133Sdg * this is for our list of users that are accumulating time. 613133Sdg */ 62239910Sedstruct user_entry { 63239910Sed SLIST_ENTRY(user_entry) next; 64239910Sed char user[sizeof(((struct utmpx *)0)->ut_user)]; 65239910Sed struct timeval time; 663133Sdg}; 673133Sdg 683133Sdg/* 693133Sdg * this is for chosing whether to ignore a login 703133Sdg */ 71239910Sedstruct tty_entry { 72239910Sed SLIST_ENTRY(tty_entry) next; 73239910Sed char line[sizeof(((struct utmpx *)0)->ut_line) + 2]; 74239910Sed size_t len; 75239910Sed int ret; 763133Sdg}; 773133Sdg 783133Sdg/* 793133Sdg * globals - yes yuk 803133Sdg */ 813133Sdg#ifdef CONSOLE_TTY 82239910Sedstatic const char *Console = CONSOLE_TTY; 833133Sdg#endif 84239910Sedstatic struct timeval Total = { 0, 0 }; 85239910Sedstatic struct timeval FirstTime = { 0, 0 }; 863133Sdgstatic int Flags = 0; 87239910Sedstatic SLIST_HEAD(, utmpx_entry) CurUtmpx = SLIST_HEAD_INITIALIZER(CurUtmpx); 88239910Sedstatic SLIST_HEAD(, user_entry) Users = SLIST_HEAD_INITIALIZER(Users); 89239910Sedstatic 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 97239910Sedstatic void ac(const char *); 98239910Sedstatic void usage(void); 993133Sdg 100239910Sedstatic void 101239910Sedadd_tty(const char *line) 1023133Sdg{ 103239910Sed struct tty_entry *tp; 104126516Sgad char *rcp; 1053133Sdg 1063133Sdg Flags |= AC_T; 1078857Srgrimes 108239910Sed if ((tp = malloc(sizeof(*tp))) == NULL) 10952166Scharnier errx(1, "malloc failed"); 1103133Sdg tp->len = 0; /* full match */ 1113133Sdg tp->ret = 1; /* do if match */ 112239910Sed if (*line == '!') { /* don't do if match */ 1133133Sdg tp->ret = 0; 114239910Sed line++; 1153133Sdg } 116239910Sed strlcpy(tp->line, line, sizeof(tp->line)); 117239910Sed /* Wildcard. */ 118239910Sed if ((rcp = strchr(tp->line, '*')) != NULL) { 1193133Sdg *rcp = '\0'; 120239910Sed /* Match len bytes only. */ 121239910Sed tp->len = strlen(tp->line); 1223133Sdg } 123239910Sed SLIST_INSERT_HEAD(&Ttys, tp, next); 1243133Sdg} 1253133Sdg 1263133Sdg/* 1273133Sdg * should we process the named tty? 1283133Sdg */ 129239910Sedstatic int 130239910Seddo_tty(const char *line) 1313133Sdg{ 132239910Sed struct tty_entry *tp; 1333133Sdg int def_ret = 0; 1348857Srgrimes 135239910Sed SLIST_FOREACH(tp, &Ttys, next) { 1363133Sdg if (tp->ret == 0) /* specific don't */ 1373133Sdg def_ret = 1; /* default do */ 1383133Sdg if (tp->len != 0) { 139239910Sed if (strncmp(line, tp->line, tp->len) == 0) 1403133Sdg return tp->ret; 1413133Sdg } else { 142239910Sed if (strncmp(line, tp->line, sizeof(tp->line)) == 0) 1433133Sdg return tp->ret; 1443133Sdg } 1453133Sdg } 146239910Sed return (def_ret); 1473133Sdg} 1483133Sdg 1493133Sdg#ifdef CONSOLE_TTY 1503133Sdg/* 1513133Sdg * is someone logged in on Console? 1523133Sdg */ 153239910Sedstatic int 154239910Sedon_console(void) 1553133Sdg{ 156239910Sed struct utmpx_entry *up; 1573133Sdg 158239910Sed SLIST_FOREACH(up, &CurUtmpx, next) 159239910Sed if (strcmp(up->line, Console) == 0) 160239910Sed return (1); 161239910Sed return (0); 1623133Sdg} 1633133Sdg#endif 1643133Sdg 1653133Sdg/* 166239910Sed * Update user's login time. 167239910Sed * If no entry for this user is found, a new entry is inserted into the 168239910Sed * list alphabetically. 1693133Sdg */ 170239910Sedstatic void 171239910Sedupdate_user(const char *user, struct timeval secs) 1723133Sdg{ 173239910Sed struct user_entry *up, *aup; 174239910Sed int c; 1753133Sdg 176239910Sed aup = NULL; 177239910Sed SLIST_FOREACH(up, &Users, next) { 178239910Sed c = strcmp(up->user, user); 179239910Sed if (c == 0) { 180239910Sed timeradd(&up->time, &secs, &up->time); 181239910Sed timeradd(&Total, &secs, &Total); 182239910Sed return; 183239910Sed } else if (c > 0) 184239910Sed break; 185239910Sed aup = up; 1863133Sdg } 1873133Sdg /* 1883133Sdg * not found so add new user unless specified users only 1893133Sdg */ 1903133Sdg if (Flags & AC_U) 191239910Sed return; 1928857Srgrimes 193239910Sed if ((up = malloc(sizeof(*up))) == NULL) 19452166Scharnier errx(1, "malloc failed"); 195239910Sed if (aup == NULL) 196239910Sed SLIST_INSERT_HEAD(&Users, up, next); 197239910Sed else 198239910Sed SLIST_INSERT_AFTER(aup, up, next); 199239910Sed strlcpy(up->user, user, sizeof(up->user)); 200239910Sed up->time = secs; 201239910Sed 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 212239910Sed 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++) { 245239910Sed 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 253239910Sed return (0); 2543133Sdg} 2553133Sdg 2563133Sdg/* 2573133Sdg * print login time in decimal hours 2583133Sdg */ 259239910Sedstatic void 260239910Sedshow(const char *user, struct timeval secs) 2613133Sdg{ 262202203Sed (void)printf("\t%-*s %8.2f\n", 263239910Sed (int)sizeof(((struct user_entry *)0)->user), user, 264239910Sed (double)secs.tv_sec / 3600); 2653133Sdg} 2663133Sdg 267239910Sedstatic void 268239910Sedshow_users(void) 2693133Sdg{ 270239910Sed struct user_entry *lp; 2713133Sdg 272239910Sed SLIST_FOREACH(lp, &Users, next) 273239910Sed show(lp->user, lp->time); 2743133Sdg} 2753133Sdg 2763133Sdg/* 2773133Sdg * print total login time for 24hr period in decimal hours 2783133Sdg */ 279239910Sedstatic void 280239910Sedshow_today(struct timeval today) 2813133Sdg{ 282239910Sed struct user_entry *up; 283239910Sed struct utmpx_entry *lp; 2843133Sdg char date[64]; 285239938Sed 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'); 290239910Sed timersub(&today, &usec, &yesterday); 291239910Sed (void)strftime(date, sizeof(date), 29274568Sache d_first ? "%e %b total" : "%b %e total", 293239910Sed localtime(&yesterday.tv_sec)); 2943133Sdg 295239910Sed SLIST_FOREACH(lp, &CurUtmpx, next) { 296239938Sed timersub(&today, &lp->time, &diff); 297239938Sed update_user(lp->user, diff); 298239910Sed /* As if they just logged in. */ 299239938Sed lp->time = today; 3003133Sdg } 301239910Sed SLIST_FOREACH(up, &Users, next) { 302239938Sed timeradd(&total, &up->time, &total); 303239910Sed /* For next day. */ 304239910Sed timerclear(&up->time); 3053133Sdg } 306239938Sed if (timerisset(&total)) 307239938Sed (void)printf("%s %11.2f\n", date, (double)total.tv_sec / 3600); 3083133Sdg} 3093133Sdg 3103133Sdg/* 311239910Sed * Log a user out and update their times. 312239910Sed * If ut_type is BOOT_TIME or SHUTDOWN_TIME, we log all users out as the 313239910Sed * system has been shut down. 3143133Sdg */ 315239910Sedstatic void 316239910Sedlog_out(const struct utmpx *up) 3173133Sdg{ 318239910Sed struct utmpx_entry *lp, *lp2, *tlp; 319239910Sed struct timeval secs; 3208857Srgrimes 321239910Sed 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 && 324239910Sed memcmp(lp->id, up->ut_id, sizeof(up->ut_id)) == 0)) { 325239910Sed timersub(&up->ut_tv, &lp->time, &secs); 326239910Sed update_user(lp->user, secs); 3273133Sdg /* 3283133Sdg * now lose it 3293133Sdg */ 3303133Sdg tlp = lp; 331239910Sed lp = SLIST_NEXT(lp, next); 332239910Sed if (lp2 == NULL) 333239910Sed SLIST_REMOVE_HEAD(&CurUtmpx, next); 334239910Sed else 335239910Sed SLIST_REMOVE_AFTER(lp2, next); 3363133Sdg free(tlp); 3373133Sdg } else { 3383133Sdg lp2 = lp; 339239910Sed lp = SLIST_NEXT(lp, next); 3403133Sdg } 3413133Sdg} 3423133Sdg 3433133Sdg/* 3443133Sdg * if do_tty says ok, login a user 3453133Sdg */ 346239910Sedstatic void 347239910Sedlog_in(struct utmpx *up) 3483133Sdg{ 349239910Sed 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 */ 368239910Sed if (on_console()) 369239910Sed return; 3703133Sdg /* 3713133Sdg * ok, no recorded login, so they were here when wtmp 3728857Srgrimes * started! Adjust ut_time! 3733133Sdg */ 374239910Sed up->ut_tv = FirstTime; 3753133Sdg /* 3763133Sdg * this allows us to pick the right logout 3773133Sdg */ 378239910Sed 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 */ 385239910Sed if (Flags & AC_T && !do_tty(up->ut_line)) 386239910Sed return; 3873133Sdg 3883133Sdg /* 3893133Sdg * go ahead and log them in 3903133Sdg */ 391239910Sed if ((lp = malloc(sizeof(*lp))) == NULL) 39252166Scharnier errx(1, "malloc failed"); 393239910Sed SLIST_INSERT_HEAD(&CurUtmpx, lp, next); 394239910Sed strlcpy(lp->user, up->ut_user, sizeof(lp->user)); 395239910Sed memcpy(lp->id, up->ut_id, sizeof(lp->id)); 396239910Sed#ifdef CONSOLE_TTY 397239910Sed memcpy(lp->line, up->ut_line, sizeof(lp->line)); 3983133Sdg#endif 399239910Sed lp->time = up->ut_tv; 4003133Sdg} 4013133Sdg 402239910Sedstatic void 403202203Sedac(const char *file) 4043133Sdg{ 405239910Sed struct utmpx_entry *lp; 406202203Sed struct utmpx *usr, usht; 4073133Sdg struct tm *ltm; 408239910Sed struct timeval prev_secs, ut_timecopy, secs, clock_shift, now; 409239910Sed int day, rfound; 4108857Srgrimes 411126752Sgad day = -1; 412239910Sed timerclear(&prev_secs); /* Minimum acceptable date == 1970. */ 413239910Sed timerclear(&secs); 414239910Sed timerclear(&clock_shift); 415239910Sed rfound = 0; 416202203Sed if (setutxdb(UTXDB_LOG, file) != 0) 417202203Sed err(1, "%s", file); 418202203Sed while ((usr = getutxent()) != NULL) { 419126752Sgad rfound++; 420239910Sed ut_timecopy = usr->ut_tv; 421239910Sed /* Don't let the time run backwards. */ 422239910Sed if (timercmp(&ut_timecopy, &prev_secs, <)) 423239910Sed ut_timecopy = prev_secs; 424126752Sgad prev_secs = ut_timecopy; 425126752Sgad 426239910Sed if (!timerisset(&FirstTime)) 427126752Sgad FirstTime = ut_timecopy; 4283133Sdg if (Flags & AC_D) { 429239910Sed 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; 436239910Sed secs.tv_sec -= ltm->tm_sec; 437239910Sed secs.tv_sec -= 60 * ltm->tm_min; 438239910Sed secs.tv_sec -= 3600 * ltm->tm_hour; 439239910Sed secs.tv_usec = 0; 440239910Sed show_today(secs); 4413133Sdg } else 4423133Sdg day = ltm->tm_yday; 4433133Sdg } 444202203Sed switch(usr->ut_type) { 445202203Sed case OLD_TIME: 446239910Sed clock_shift = ut_timecopy; 4473133Sdg break; 448202203Sed case NEW_TIME: 449239910Sed timersub(&clock_shift, &ut_timecopy, &clock_shift); 4503133Sdg /* 4513133Sdg * adjust time for those logged in 4523133Sdg */ 453239910Sed SLIST_FOREACH(lp, &CurUtmpx, next) 454239910Sed timersub(&lp->time, &clock_shift, &lp->time); 4553133Sdg break; 456202203Sed case BOOT_TIME: 457202203Sed case SHUTDOWN_TIME: 458239910Sed log_out(usr); 459126752Sgad FirstTime = ut_timecopy; /* shouldn't be needed */ 4603133Sdg break; 461202203Sed case USER_PROCESS: 4623133Sdg /* 463239910Sed * If they came in on pts/..., then it is only 464239910Sed * a login session if the ut_host field is non-empty. 4653133Sdg */ 466239910Sed if (strncmp(usr->ut_line, "pts/", 4) != 0 || 467202203Sed *usr->ut_host != '\0') 468239910Sed log_in(usr); 4693133Sdg break; 470202203Sed case DEAD_PROCESS: 471239910Sed log_out(usr); 472202203Sed break; 4733133Sdg } 4743133Sdg } 475202203Sed endutxent(); 476239910Sed (void)gettimeofday(&now, NULL); 477239910Sed if (Flags & AC_W) 478239910Sed usht.ut_tv = ut_timecopy; 479206095Sed else 480239910Sed usht.ut_tv = now; 481202203Sed usht.ut_type = SHUTDOWN_TIME; 4828857Srgrimes 4833133Sdg if (Flags & AC_D) { 484239910Sed 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; 490239910Sed secs.tv_sec -= ltm->tm_sec; 491239910Sed secs.tv_sec -= 60 * ltm->tm_min; 492239910Sed secs.tv_sec -= 3600 * ltm->tm_hour; 493239910Sed secs.tv_usec = 0; 494239910Sed show_today(secs); 4953133Sdg } 4963133Sdg } 4973133Sdg /* 4983133Sdg * anyone still logged in gets time up to now 4993133Sdg */ 500239910Sed log_out(&usht); 5013133Sdg 5023133Sdg if (Flags & AC_D) 503239910Sed show_today(now); 5043133Sdg else { 5053133Sdg if (Flags & AC_P) 506239910Sed show_users(); 5073133Sdg show("total", Total); 5083133Sdg } 5093133Sdg} 5103133Sdg 511239910Sedstatic 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