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