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