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