1/*
2 * Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * "Portions Copyright (c) 1999 Apple Computer, Inc.  All Rights
7 * Reserved.  This file contains Original Code and/or Modifications of
8 * Original Code as defined in and that are subject to the Apple Public
9 * Source License Version 1.0 (the 'License').  You may not use this file
10 * except in compliance with the License.  Please obtain a copy of the
11 * License at http://www.apple.com/publicsource and read it before using
12 * this file.
13 *
14 * The Original Code and all software distributed under the License are
15 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
16 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
17 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
19 * License for the specific language governing rights and limitations
20 * under the License."
21 *
22 * @APPLE_LICENSE_HEADER_END@
23 */
24/*
25 *      Copyright (c) 1994 Christopher G. Demetriou.
26 *      @(#)Copyright (c) 1994, Simon J. Gerraty.
27 *
28 *      This is free software.  It comes with NO WARRANTY.
29 *      Permission to use, modify and distribute this source code
30 *      is granted subject to the following conditions.
31 *      1/ that the above copyright notice and this notice
32 *      are preserved in all copies and that due credit be given
33 *      to the author.
34 *      2/ that any changes to this code are clearly commented
35 *      as such so that the author does not get blamed for bugs
36 *      other than his own.
37 */
38
39#ifndef lint
40#include <sys/cdefs.h>
41__unused static char rcsid[] = "$Id: ac.c,v 1.2 2006/02/07 05:51:22 lindak Exp $";
42#endif
43
44#include <sys/types.h>
45#include <sys/file.h>
46#include <sys/time.h>
47#include <err.h>
48#include <errno.h>
49#include <pwd.h>
50#include <stdio.h>
51#include <stdlib.h>
52#include <string.h>
53#include <utmpx.h>
54#include <unistd.h>
55
56#define UT_NAMESIZE 8 /* from utmp.h; only for formatting */
57
58/*
59 * this is for our list of currently logged in sessions
60 */
61struct utmp_list {
62	struct utmp_list *next;
63	struct utmpx usr;
64};
65
66/*
67 * this is for our list of users that are accumulating time.
68 */
69struct user_list {
70	struct user_list *next;
71	char	name[_UTX_USERSIZE+1];
72	time_t	secs;
73};
74
75/*
76 * this is for chosing whether to ignore a login
77 */
78struct tty_list {
79	struct tty_list *next;
80	char	name[_UTX_USERSIZE+3];
81	int	len;
82	int	ret;
83};
84
85/*
86 * globals - yes yuk
87 */
88#ifdef CONSOLE_TTY
89static char 	*Console = CONSOLE_TTY;
90#endif
91static time_t	Total = 0;
92static time_t	FirstTime = 0;
93static int	Flags = 0;
94static struct user_list *Users = NULL;
95static struct tty_list *Ttys = NULL;
96
97#define NEW(type) (type *)malloc(sizeof (type))
98
99#define	AC_W	1				/* not _PATH_WTMP */
100#define	AC_D	2				/* daily totals (ignore -p) */
101#define	AC_P	4				/* per-user totals */
102#define	AC_U	8				/* specified users only */
103#define	AC_T	16				/* specified ttys only */
104
105#ifdef DEBUG
106static int Debug = 0;
107#endif
108
109int			main __P((int, char **));
110int			ac __P((void));
111struct tty_list		*add_tty __P((char *));
112int			do_tty __P((char *));
113struct utmp_list	*log_in __P((struct utmp_list *, struct utmpx *));
114struct utmp_list	*log_out __P((struct utmp_list *, struct utmpx *));
115int			on_console __P((struct utmp_list *));
116void			show __P((char *, time_t));
117void			show_today __P((struct user_list *, struct utmp_list *,
118			    time_t));
119void			show_users __P((struct user_list *));
120struct user_list	*update_user __P((struct user_list *, char *, time_t));
121void			usage __P((void));
122
123struct tty_list *
124add_tty(name)
125	char *name;
126{
127	struct tty_list *tp;
128	register char *rcp;
129
130	Flags |= AC_T;
131
132	if ((tp = NEW(struct tty_list)) == NULL)
133		err(1, "malloc");
134	tp->len = 0;				/* full match */
135	tp->ret = 1;				/* do if match */
136	if (*name == '!') {			/* don't do if match */
137		tp->ret = 0;
138		name++;
139	}
140	(void)strncpy(tp->name, name, sizeof (tp->name) - 1);
141	tp->name[sizeof (tp->name) - 1] = '\0';
142	if ((rcp = strchr(tp->name, '*')) != NULL) {	/* wild card */
143		*rcp = '\0';
144		tp->len = (int)strlen(tp->name);	/* match len bytes only */
145	}
146	tp->next = Ttys;
147	Ttys = tp;
148	return Ttys;
149}
150
151/*
152 * should we process the named tty?
153 */
154int
155do_tty(name)
156	char *name;
157{
158	struct tty_list *tp;
159	int def_ret = 0;
160
161	for (tp = Ttys; tp != NULL; tp = tp->next) {
162		if (tp->ret == 0)		/* specific don't */
163			def_ret = 1;		/* default do */
164		if (tp->len != 0) {
165			if (strncmp(name, tp->name, tp->len) == 0)
166				return tp->ret;
167		} else {
168			if (strncmp(name, tp->name, sizeof (tp->name)) == 0)
169				return tp->ret;
170		}
171	}
172	return def_ret;
173}
174
175#ifdef CONSOLE_TTY
176/*
177 * is someone logged in on Console?
178 */
179int
180on_console(head)
181	struct utmp_list *head;
182{
183	struct utmp_list *up;
184
185	for (up = head; up; up = up->next) {
186		if (strncmp(up->usr.ut_line, Console,
187		    sizeof (up->usr.ut_line)) == 0)
188			return 1;
189	}
190	return 0;
191}
192#endif
193
194/*
195 * update user's login time
196 */
197struct user_list *
198update_user(head, name, secs)
199	struct user_list *head;
200	char	*name;
201	time_t	secs;
202{
203	struct user_list *up;
204
205	for (up = head; up != NULL; up = up->next) {
206		if (strncmp(up->name, name, sizeof (up->name)) == 0) {
207			up->secs += secs;
208			Total += secs;
209			return head;
210		}
211	}
212	/*
213	 * not found so add new user unless specified users only
214	 */
215	if (Flags & AC_U)
216		return head;
217
218	if ((up = NEW(struct user_list)) == NULL)
219		err(1, "malloc");
220	up->next = head;
221	(void)strncpy(up->name, name, sizeof (up->name) - 1);
222	up->name[sizeof (up->name) - 1] = '\0';	/* paranoid! */
223	up->secs = secs;
224	Total += secs;
225	return up;
226}
227
228int
229main(argc, argv)
230	int	argc;
231	char	**argv;
232{
233	FILE *fp;
234	int c;
235
236	fp = NULL;
237	while ((c = getopt(argc, argv, "Dc:dpt:w:")) != EOF) {
238		switch (c) {
239#ifdef DEBUG
240		case 'D':
241			Debug++;
242			break;
243#endif
244		case 'c':
245#ifdef CONSOLE_TTY
246			Console = optarg;
247#else
248			usage();		/* XXX */
249#endif
250			break;
251		case 'd':
252			Flags |= AC_D;
253			break;
254		case 'p':
255			Flags |= AC_P;
256			break;
257		case 't':			/* only do specified ttys */
258			add_tty(optarg);
259			break;
260		case 'w':
261			wtmpxname(optarg);
262			break;
263		case '?':
264		default:
265			usage();
266			break;
267		}
268	}
269	if (optind < argc) {
270		/*
271		 * initialize user list
272		 */
273		for (; optind < argc; optind++) {
274			Users = update_user(Users, argv[optind], 0L);
275		}
276		Flags |= AC_U;			/* freeze user list */
277	}
278	if (Flags & AC_D)
279		Flags &= ~AC_P;
280	ac();
281
282	return 0;
283}
284
285/*
286 * print login time in decimal hours
287 */
288void
289show(name, secs)
290	char *name;
291	time_t secs;
292{
293	(void)printf("\t%-*s %8.2f\n", UT_NAMESIZE, name,
294	    ((double)secs / 3600));
295}
296
297void
298show_users(list)
299	struct user_list *list;
300{
301	struct user_list *lp;
302
303	for (lp = list; lp; lp = lp->next)
304		show(lp->name, lp->secs);
305}
306
307/*
308 * print total login time for 24hr period in decimal hours
309 */
310void
311show_today(users, logins, secs)
312	struct user_list *users;
313	struct utmp_list *logins;
314	time_t secs;
315{
316	struct user_list *up;
317	struct utmp_list *lp;
318	char date[64];
319	time_t yesterday = secs - 1;
320
321	(void)strftime(date, sizeof (date), "%b %e  total",
322	    localtime(&yesterday));
323
324	/* restore the missing second */
325	yesterday++;
326
327	for (lp = logins; lp != NULL; lp = lp->next) {
328		secs = yesterday - lp->usr.ut_tv.tv_sec;
329		Users = update_user(Users, lp->usr.ut_user, secs);
330		lp->usr.ut_tv.tv_sec = yesterday;	/* as if they just logged in */
331	}
332	secs = 0;
333	for (up = users; up != NULL; up = up->next) {
334		secs += up->secs;
335		up->secs = 0;			/* for next day */
336	}
337 	if (secs)
338		(void)printf("%s %11.2f\n", date, ((double)secs / 3600));
339}
340
341/*
342 * log a user out and update their times.
343 * if ut_line is "~", we log all users out as the system has
344 * been shut down.
345 */
346struct utmp_list *
347log_out(head, up)
348	struct utmp_list *head;
349	struct utmpx *up;
350{
351	struct utmp_list *lp, *lp2, *tlp;
352	time_t secs;
353
354	for (lp = head, lp2 = NULL; lp != NULL; )
355		if (up->ut_type == BOOT_TIME || up->ut_type == SHUTDOWN_TIME || strncmp(lp->usr.ut_line, up->ut_line,
356		    sizeof (up->ut_line)) == 0) {
357			secs = up->ut_tv.tv_sec - lp->usr.ut_tv.tv_sec;
358			Users = update_user(Users, lp->usr.ut_user, secs);
359#ifdef DEBUG
360			if (Debug)
361				printf("%-.*s %-.*s: %-.*s logged out (%2d:%02d:%02d)\n",
362				    19, ctime(&up->ut_tv.tv_sec),
363				    (int)sizeof (lp->usr.ut_line), lp->usr.ut_line,
364				    (int)sizeof (lp->usr.ut_user), lp->usr.ut_user,
365				    secs / 3600, (secs % 3600) / 60, secs % 60);
366#endif
367			/*
368			 * now lose it
369			 */
370			tlp = lp;
371			lp = lp->next;
372			if (tlp == head)
373				head = lp;
374			else if (lp2 != NULL)
375				lp2->next = lp;
376			free(tlp);
377		} else {
378			lp2 = lp;
379			lp = lp->next;
380		}
381	return head;
382}
383
384
385/*
386 * if do_tty says ok, login a user
387 */
388struct utmp_list *
389log_in(head, up)
390	struct utmp_list *head;
391	struct utmpx *up;
392{
393	struct utmp_list *lp;
394
395	/*
396	 * this could be a login. if we're not dealing with
397	 * the console name, say it is.
398	 *
399	 * If we are, and if ut_host==":0.0" we know that it
400	 * isn't a real login. _But_ if we have not yet recorded
401	 * someone being logged in on Console - due to the wtmp
402	 * file starting after they logged in, we'll pretend they
403	 * logged in, at the start of the wtmp file.
404	 */
405
406#ifdef CONSOLE_TTY
407	if (up->ut_host[0] == ':') {
408		/*
409		 * SunOS 4.0.2 does not treat ":0.0" as special but we
410		 * do.
411		 */
412		if (on_console(head))
413			return head;
414		/*
415		 * ok, no recorded login, so they were here when wtmp
416		 * started!  Adjust ut_tv.tv_sec!
417		 */
418		up->ut_tv.tv_sec = FirstTime;
419		/*
420		 * this allows us to pick the right logout
421		 */
422		(void)strncpy(up->ut_line, Console, sizeof (up->ut_line) - 1);
423		up->ut_line[sizeof (up->ut_line) - 1] = '\0'; /* paranoid! */
424	}
425#endif
426	/*
427	 * If we are doing specified ttys only, we ignore
428	 * anything else.
429	 */
430	if (Flags & AC_T)
431		if (!do_tty(up->ut_line))
432			return head;
433
434	/*
435	 * go ahead and log them in
436	 */
437	if ((lp = NEW(struct utmp_list)) == NULL)
438		err(1, "malloc");
439	lp->next = head;
440	head = lp;
441	memmove((char *)&lp->usr, (char *)up, sizeof (struct utmpx));
442#ifdef DEBUG
443	if (Debug) {
444		printf("%-.*s %-.*s: %-.*s logged in", 19,
445		    ctime(&lp->usr.ut_tv.tv_sec), (int)sizeof (up->ut_line),
446		       up->ut_line, (int)sizeof (up->ut_user), up->ut_user);
447		if (*up->ut_host)
448			printf(" (%-.*s)", (int)sizeof (up->ut_host), up->ut_host);
449		putchar('\n');
450	}
451#endif
452	return head;
453}
454
455int
456ac()
457{
458	struct utmp_list *lp, *head = NULL;
459	struct utmpx *u, end;
460	struct tm *ltm;
461	time_t secs = 0;
462	int day = -1;
463
464	setutxent_wtmp(1); /* read in forward direction */
465	while ((u = getutxent_wtmp()) != NULL) {
466		if (!FirstTime)
467			FirstTime = u->ut_tv.tv_sec;
468		if (Flags & AC_D) {
469			ltm = localtime(&u->ut_tv.tv_sec);
470			if (day >= 0 && day != ltm->tm_yday) {
471				day = ltm->tm_yday;
472				/*
473				 * print yesterday's total
474				 */
475				secs = u->ut_tv.tv_sec;
476				secs -= ltm->tm_sec;
477				secs -= 60 * ltm->tm_min;
478				secs -= 3600 * ltm->tm_hour;
479				show_today(Users, head, secs);
480			} else
481				day = ltm->tm_yday;
482		}
483		switch(u->ut_type) {
484		case OLD_TIME:
485			secs = u->ut_tv.tv_sec;
486			break;
487		case NEW_TIME:
488			secs -= u->ut_tv.tv_sec;
489			/*
490			 * adjust time for those logged in
491			 */
492			for (lp = head; lp != NULL; lp = lp->next)
493				lp->usr.ut_tv.tv_sec -= secs;
494			break;
495		case BOOT_TIME:			/* reboot or shutdown */
496		case SHUTDOWN_TIME:
497			head = log_out(head, u);
498			FirstTime = u->ut_tv.tv_sec; /* shouldn't be needed */
499			break;
500		case USER_PROCESS:
501			/*
502			 * if they came in on tty[p-y]*, then it is only
503			 * a login session if the ut_host field is non-empty
504			 */
505			if (strncmp(u->ut_line, "tty", 3) != 0 ||
506			    strchr("pqrstuvwxy", u->ut_line[3]) == 0 ||
507			    *u->ut_host != '\0')
508				head = log_in(head, u);
509			break;
510		case DEAD_PROCESS:
511			head = log_out(head, u);
512			break;
513		}
514	}
515	endutxent_wtmp();
516	bzero(&end, sizeof(end));
517	end.ut_tv.tv_sec = time((time_t *)0);
518	end.ut_type = SHUTDOWN_TIME;
519
520	if (Flags & AC_D) {
521		ltm = localtime(&end.ut_tv.tv_sec);
522		if (day >= 0 && day != ltm->tm_yday) {
523			/*
524			 * print yesterday's total
525			 */
526			secs = end.ut_tv.tv_sec;
527			secs -= ltm->tm_sec;
528			secs -= 60 * ltm->tm_min;
529			secs -= 3600 * ltm->tm_hour;
530			show_today(Users, head, secs);
531		}
532	}
533	/*
534	 * anyone still logged in gets time up to now
535	 */
536	head = log_out(head, &end);
537
538	if (Flags & AC_D)
539		show_today(Users, head, time((time_t *)0));
540	else {
541		if (Flags & AC_P)
542			show_users(Users);
543		show("total", Total);
544	}
545	return 0;
546}
547
548void
549usage()
550{
551	(void)fprintf(stderr,
552#ifdef CONSOLE_TTY
553	    "ac [-dp] [-c console] [-t tty] [-w wtmp] [users ...]\n");
554#else
555	    "ac [-dp] [-t tty] [-w wtmp] [users ...]\n");
556#endif
557	exit(1);
558}
559