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