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