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