ac.c revision 1.19 1 /* $NetBSD: ac.c,v 1.19 2004/01/06 13:28:20 wiz 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.19 2004/01/06 13:28:20 wiz 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)strlcpy(tp->name, name, sizeof (tp->name));
178 if ((rcp = strchr(tp->name, '*')) != NULL) { /* wild card */
179 *rcp = '\0';
180 tp->len = strlen(tp->name); /* match len bytes only */
181 }
182 tp->next = Ttys;
183 Ttys = tp;
184 return Ttys;
185 }
186
187 /*
188 * should we process the named tty?
189 */
190 static int
191 do_tty(name)
192 char *name;
193 {
194 struct tty_list *tp;
195 int def_ret = 0;
196
197 for (tp = Ttys; tp != NULL; tp = tp->next) {
198 if (tp->ret == 0) /* specific don't */
199 def_ret = 1; /* default do */
200 if (tp->len != 0) {
201 if (strncmp(name, tp->name, tp->len) == 0)
202 return tp->ret;
203 } else {
204 if (strncmp(name, tp->name, sizeof (tp->name)) == 0)
205 return tp->ret;
206 }
207 }
208 return def_ret;
209 }
210
211 static int
212 compare(a, b)
213 const void *a, *b;
214 {
215 return strncmp(a, b, UT_LINESIZE);
216 }
217
218 /*
219 * Deal correctly with multiple virtual consoles/login ttys.
220 * We read the ttyent's from /etc/ttys and classify as login
221 * ttys ones that are running getty and they are turned on.
222 */
223 static void
224 find_login_ttys()
225 {
226 struct ttyent *tty;
227 char (*nCon)[UT_LINESIZE];
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 ((nCon = realloc(Con, (Maxcon + 10) *
238 sizeof(Con[0]))) == NULL)
239 err(1, "malloc");
240 Con = nCon;
241 Maxcon += 10;
242 }
243 (void)strncpy(Con[Ncon++], tty->ty_name, UT_LINESIZE);
244 }
245 endttyent();
246 qsort(Con, Ncon, sizeof(Con[0]), compare);
247 }
248
249 #ifdef notdef
250 /*
251 * is someone logged in on Console/login tty?
252 */
253 static int
254 on_console(head)
255 struct utmp_list *head;
256 {
257 struct utmp_list *up;
258
259 for (up = head; up; up = up->next)
260 if (is_login_tty(up->usr.ut_line))
261 return 1;
262
263 return 0;
264 }
265 #endif
266
267 /*
268 * update user's login time
269 */
270 static struct user_list *
271 update_user(head, name, secs)
272 struct user_list *head;
273 char *name;
274 time_t secs;
275 {
276 struct user_list *up;
277
278 for (up = head; up != NULL; up = up->next) {
279 if (strncmp(up->name, name, sizeof (up->name) - 1) == 0) {
280 up->secs += secs;
281 Total += secs;
282 return head;
283 }
284 }
285 /*
286 * not found so add new user unless specified users only
287 */
288 if (Flags & AC_U)
289 return head;
290
291 if ((up = NEW(struct user_list)) == NULL)
292 err(1, "malloc");
293 up->next = head;
294 (void)strlcpy(up->name, name, sizeof (up->name));
295 up->secs = secs;
296 Total += secs;
297 return up;
298 }
299
300 int
301 main(argc, argv)
302 int argc;
303 char **argv;
304 {
305 FILE *fp;
306 int c;
307
308 fp = NULL;
309 while ((c = getopt(argc, argv, "Ddpt:w:")) != -1) {
310 switch (c) {
311 #ifdef DEBUG
312 case 'D':
313 Debug++;
314 break;
315 #endif
316 case 'd':
317 Flags |= AC_D;
318 break;
319 case 'p':
320 Flags |= AC_P;
321 break;
322 case 't': /* only do specified ttys */
323 add_tty(optarg);
324 break;
325 case 'w':
326 fp = file(optarg);
327 break;
328 case '?':
329 default:
330 usage();
331 }
332 }
333
334 find_login_ttys();
335
336 if (optind < argc) {
337 /*
338 * initialize user list
339 */
340 for (; optind < argc; optind++) {
341 Users = update_user(Users, argv[optind], 0L);
342 }
343 Flags |= AC_U; /* freeze user list */
344 }
345 if (Flags & AC_D)
346 Flags &= ~AC_P;
347 if (fp == NULL) {
348 /*
349 * if _PATH_WTMP does not exist, exit quietly
350 */
351 if (access(_PATH_WTMP, 0) != 0 && errno == ENOENT)
352 return 0;
353
354 fp = file(_PATH_WTMP);
355 }
356 ac(fp);
357
358 return 0;
359 }
360
361 /*
362 * print login time in decimal hours
363 */
364 static void
365 show(name, secs)
366 char *name;
367 time_t secs;
368 {
369 (void)printf("\t%-*s %8.2f\n", UT_NAMESIZE, name,
370 ((double)secs / 3600));
371 }
372
373 static void
374 show_users(list)
375 struct user_list *list;
376 {
377 struct user_list *lp;
378
379 for (lp = list; lp; lp = lp->next)
380 show(lp->name, lp->secs);
381 }
382
383 /*
384 * print total login time for 24hr period in decimal hours
385 */
386 static void
387 show_today(users, logins, secs)
388 struct user_list *users;
389 struct utmp_list *logins;
390 time_t secs;
391 {
392 struct user_list *up;
393 struct utmp_list *lp;
394 char date[64];
395 time_t yesterday = secs - 1;
396
397 (void)strftime(date, sizeof (date), "%b %e total",
398 localtime(&yesterday));
399
400 /* restore the missing second */
401 yesterday++;
402
403 for (lp = logins; lp != NULL; lp = lp->next) {
404 secs = yesterday - lp->usr.ut_time;
405 Users = update_user(Users, lp->usr.ut_name, secs);
406 lp->usr.ut_time = yesterday; /* as if they just logged in */
407 }
408 secs = 0;
409 for (up = users; up != NULL; up = up->next) {
410 secs += up->secs;
411 up->secs = 0; /* for next day */
412 }
413 if (secs)
414 (void)printf("%s %11.2f\n", date, ((double)secs / 3600));
415 }
416
417 /*
418 * log a user out and update their times.
419 * if ut_line is "~", we log all users out as the system has
420 * been shut down.
421 */
422 static struct utmp_list *
423 log_out(head, up)
424 struct utmp_list *head;
425 struct utmp *up;
426 {
427 struct utmp_list *lp, *lp2, *tlp;
428 time_t secs;
429
430 for (lp = head, lp2 = NULL; lp != NULL; )
431 if (*up->ut_line == '~' || strncmp(lp->usr.ut_line, up->ut_line,
432 sizeof (up->ut_line)) == 0) {
433 secs = up->ut_time - lp->usr.ut_time;
434 Users = update_user(Users, lp->usr.ut_name, secs);
435 #ifdef DEBUG
436 if (Debug)
437 printf("%-.*s %-.*s: %-.*s logged out (%2d:%02d:%02d)\n",
438 19, ctime(&up->ut_time),
439 sizeof (lp->usr.ut_line), lp->usr.ut_line,
440 sizeof (lp->usr.ut_name), lp->usr.ut_name,
441 secs / 3600, (secs % 3600) / 60, secs % 60);
442 #endif
443 /*
444 * now lose it
445 */
446 tlp = lp;
447 lp = lp->next;
448 if (tlp == head)
449 head = lp;
450 else if (lp2 != NULL)
451 lp2->next = lp;
452 free(tlp);
453 } else {
454 lp2 = lp;
455 lp = lp->next;
456 }
457 return head;
458 }
459
460
461 /*
462 * if do_tty says ok, login a user
463 */
464 struct utmp_list *
465 log_in(head, up)
466 struct utmp_list *head;
467 struct utmp *up;
468 {
469 struct utmp_list *lp;
470
471 /*
472 * If we are doing specified ttys only, we ignore
473 * anything else.
474 */
475 if (Flags & AC_T)
476 if (!do_tty(up->ut_line))
477 return head;
478
479 /*
480 * go ahead and log them in
481 */
482 if ((lp = NEW(struct utmp_list)) == NULL)
483 err(1, "malloc");
484 lp->next = head;
485 head = lp;
486 memmove((char *)&lp->usr, (char *)up, sizeof (struct utmp));
487 #ifdef DEBUG
488 if (Debug) {
489 printf("%-.*s %-.*s: %-.*s logged in", 19,
490 ctime(&lp->usr.ut_time), sizeof (up->ut_line),
491 up->ut_line, sizeof (up->ut_name), up->ut_name);
492 if (*up->ut_host)
493 printf(" (%-.*s)", sizeof (up->ut_host), up->ut_host);
494 putchar('\n');
495 }
496 #endif
497 return head;
498 }
499
500 static int
501 ac(fp)
502 FILE *fp;
503 {
504 struct utmp_list *lp, *head = NULL;
505 struct utmp usr;
506 struct tm *ltm;
507 time_t secs = 0;
508 int day = -1;
509
510 while (fread((char *)&usr, sizeof(usr), 1, fp) == 1) {
511 if (!FirstTime)
512 FirstTime = usr.ut_time;
513 if (Flags & AC_D) {
514 ltm = localtime(&usr.ut_time);
515 if (day >= 0 && day != ltm->tm_yday) {
516 day = ltm->tm_yday;
517 /*
518 * print yesterday's total
519 */
520 secs = usr.ut_time;
521 secs -= ltm->tm_sec;
522 secs -= 60 * ltm->tm_min;
523 secs -= 3600 * ltm->tm_hour;
524 show_today(Users, head, secs);
525 } else
526 day = ltm->tm_yday;
527 }
528 switch(*usr.ut_line) {
529 case '|':
530 secs = usr.ut_time;
531 break;
532 case '{':
533 secs -= usr.ut_time;
534 /*
535 * adjust time for those logged in
536 */
537 for (lp = head; lp != NULL; lp = lp->next)
538 lp->usr.ut_time -= secs;
539 break;
540 case '~': /* reboot or shutdown */
541 head = log_out(head, &usr);
542 FirstTime = usr.ut_time; /* shouldn't be needed */
543 break;
544 default:
545 /*
546 * if they came in on tty[p-y]*, then it is only
547 * a login session if the ut_host field is non-empty,
548 * or this tty is a login tty [eg. a console]
549 */
550 if (*usr.ut_name) {
551 if ((strncmp(usr.ut_line, "tty", 3) != 0 &&
552 strncmp(usr.ut_line, "dty", 3) != 0) ||
553 strchr("pqrstuvwxyzPQRST", usr.ut_line[3]) == 0 ||
554 *usr.ut_host != '\0' ||
555 is_login_tty(usr.ut_line))
556 head = log_in(head, &usr);
557 #ifdef DEBUG
558 else if (Debug)
559 printf("%-.*s %-.*s: %-.*s ignored\n",
560 19, ctime(&usr.ut_time),
561 sizeof (usr.ut_line), usr.ut_line,
562 sizeof (usr.ut_name), usr.ut_name);
563 #endif
564 } else
565 head = log_out(head, &usr);
566 break;
567 }
568 }
569 (void)fclose(fp);
570 usr.ut_time = time((time_t *)0);
571 (void)strcpy(usr.ut_line, "~");
572
573 if (Flags & AC_D) {
574 ltm = localtime(&usr.ut_time);
575 if (day >= 0 && day != ltm->tm_yday) {
576 /*
577 * print yesterday's total
578 */
579 secs = usr.ut_time;
580 secs -= ltm->tm_sec;
581 secs -= 60 * ltm->tm_min;
582 secs -= 3600 * ltm->tm_hour;
583 show_today(Users, head, secs);
584 }
585 }
586 /*
587 * anyone still logged in gets time up to now
588 */
589 head = log_out(head, &usr);
590
591 if (Flags & AC_D)
592 show_today(Users, head, time((time_t *)0));
593 else {
594 if (Flags & AC_P)
595 show_users(Users);
596 show("total", Total);
597 }
598 return 0;
599 }
600
601 static void
602 usage()
603 {
604
605 (void)fprintf(stderr,
606 "usage: %s [-d | -p] [-t tty] [-w wtmp] [users ...]\n",
607 getprogname());
608 exit(1);
609 }
610