ac.c revision 1.18 1 /* $NetBSD: ac.c,v 1.18 2004/01/05 23:23:37 jmmv 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.18 2004/01/05 23:23:37 jmmv 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, "Dc:dpt: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 break;
332 }
333 }
334
335 find_login_ttys();
336
337 if (optind < argc) {
338 /*
339 * initialize user list
340 */
341 for (; optind < argc; optind++) {
342 Users = update_user(Users, argv[optind], 0L);
343 }
344 Flags |= AC_U; /* freeze user list */
345 }
346 if (Flags & AC_D)
347 Flags &= ~AC_P;
348 if (fp == NULL) {
349 /*
350 * if _PATH_WTMP does not exist, exit quietly
351 */
352 if (access(_PATH_WTMP, 0) != 0 && errno == ENOENT)
353 return 0;
354
355 fp = file(_PATH_WTMP);
356 }
357 ac(fp);
358
359 return 0;
360 }
361
362 /*
363 * print login time in decimal hours
364 */
365 static void
366 show(name, secs)
367 char *name;
368 time_t secs;
369 {
370 (void)printf("\t%-*s %8.2f\n", UT_NAMESIZE, name,
371 ((double)secs / 3600));
372 }
373
374 static void
375 show_users(list)
376 struct user_list *list;
377 {
378 struct user_list *lp;
379
380 for (lp = list; lp; lp = lp->next)
381 show(lp->name, lp->secs);
382 }
383
384 /*
385 * print total login time for 24hr period in decimal hours
386 */
387 static void
388 show_today(users, logins, secs)
389 struct user_list *users;
390 struct utmp_list *logins;
391 time_t secs;
392 {
393 struct user_list *up;
394 struct utmp_list *lp;
395 char date[64];
396 time_t yesterday = secs - 1;
397
398 (void)strftime(date, sizeof (date), "%b %e total",
399 localtime(&yesterday));
400
401 /* restore the missing second */
402 yesterday++;
403
404 for (lp = logins; lp != NULL; lp = lp->next) {
405 secs = yesterday - lp->usr.ut_time;
406 Users = update_user(Users, lp->usr.ut_name, secs);
407 lp->usr.ut_time = yesterday; /* as if they just logged in */
408 }
409 secs = 0;
410 for (up = users; up != NULL; up = up->next) {
411 secs += up->secs;
412 up->secs = 0; /* for next day */
413 }
414 if (secs)
415 (void)printf("%s %11.2f\n", date, ((double)secs / 3600));
416 }
417
418 /*
419 * log a user out and update their times.
420 * if ut_line is "~", we log all users out as the system has
421 * been shut down.
422 */
423 static struct utmp_list *
424 log_out(head, up)
425 struct utmp_list *head;
426 struct utmp *up;
427 {
428 struct utmp_list *lp, *lp2, *tlp;
429 time_t secs;
430
431 for (lp = head, lp2 = NULL; lp != NULL; )
432 if (*up->ut_line == '~' || strncmp(lp->usr.ut_line, up->ut_line,
433 sizeof (up->ut_line)) == 0) {
434 secs = up->ut_time - lp->usr.ut_time;
435 Users = update_user(Users, lp->usr.ut_name, secs);
436 #ifdef DEBUG
437 if (Debug)
438 printf("%-.*s %-.*s: %-.*s logged out (%2d:%02d:%02d)\n",
439 19, ctime(&up->ut_time),
440 sizeof (lp->usr.ut_line), lp->usr.ut_line,
441 sizeof (lp->usr.ut_name), lp->usr.ut_name,
442 secs / 3600, (secs % 3600) / 60, secs % 60);
443 #endif
444 /*
445 * now lose it
446 */
447 tlp = lp;
448 lp = lp->next;
449 if (tlp == head)
450 head = lp;
451 else if (lp2 != NULL)
452 lp2->next = lp;
453 free(tlp);
454 } else {
455 lp2 = lp;
456 lp = lp->next;
457 }
458 return head;
459 }
460
461
462 /*
463 * if do_tty says ok, login a user
464 */
465 struct utmp_list *
466 log_in(head, up)
467 struct utmp_list *head;
468 struct utmp *up;
469 {
470 struct utmp_list *lp;
471
472 /*
473 * If we are doing specified ttys only, we ignore
474 * anything else.
475 */
476 if (Flags & AC_T)
477 if (!do_tty(up->ut_line))
478 return head;
479
480 /*
481 * go ahead and log them in
482 */
483 if ((lp = NEW(struct utmp_list)) == NULL)
484 err(1, "malloc");
485 lp->next = head;
486 head = lp;
487 memmove((char *)&lp->usr, (char *)up, sizeof (struct utmp));
488 #ifdef DEBUG
489 if (Debug) {
490 printf("%-.*s %-.*s: %-.*s logged in", 19,
491 ctime(&lp->usr.ut_time), sizeof (up->ut_line),
492 up->ut_line, sizeof (up->ut_name), up->ut_name);
493 if (*up->ut_host)
494 printf(" (%-.*s)", sizeof (up->ut_host), up->ut_host);
495 putchar('\n');
496 }
497 #endif
498 return head;
499 }
500
501 static int
502 ac(fp)
503 FILE *fp;
504 {
505 struct utmp_list *lp, *head = NULL;
506 struct utmp usr;
507 struct tm *ltm;
508 time_t secs = 0;
509 int day = -1;
510
511 while (fread((char *)&usr, sizeof(usr), 1, fp) == 1) {
512 if (!FirstTime)
513 FirstTime = usr.ut_time;
514 if (Flags & AC_D) {
515 ltm = localtime(&usr.ut_time);
516 if (day >= 0 && day != ltm->tm_yday) {
517 day = ltm->tm_yday;
518 /*
519 * print yesterday's total
520 */
521 secs = usr.ut_time;
522 secs -= ltm->tm_sec;
523 secs -= 60 * ltm->tm_min;
524 secs -= 3600 * ltm->tm_hour;
525 show_today(Users, head, secs);
526 } else
527 day = ltm->tm_yday;
528 }
529 switch(*usr.ut_line) {
530 case '|':
531 secs = usr.ut_time;
532 break;
533 case '{':
534 secs -= usr.ut_time;
535 /*
536 * adjust time for those logged in
537 */
538 for (lp = head; lp != NULL; lp = lp->next)
539 lp->usr.ut_time -= secs;
540 break;
541 case '~': /* reboot or shutdown */
542 head = log_out(head, &usr);
543 FirstTime = usr.ut_time; /* shouldn't be needed */
544 break;
545 default:
546 /*
547 * if they came in on tty[p-y]*, then it is only
548 * a login session if the ut_host field is non-empty,
549 * or this tty is a login tty [eg. a console]
550 */
551 if (*usr.ut_name) {
552 if ((strncmp(usr.ut_line, "tty", 3) != 0 &&
553 strncmp(usr.ut_line, "dty", 3) != 0) ||
554 strchr("pqrstuvwxyzPQRST", usr.ut_line[3]) == 0 ||
555 *usr.ut_host != '\0' ||
556 is_login_tty(usr.ut_line))
557 head = log_in(head, &usr);
558 #ifdef DEBUG
559 else if (Debug)
560 printf("%-.*s %-.*s: %-.*s ignored\n",
561 19, ctime(&usr.ut_time),
562 sizeof (usr.ut_line), usr.ut_line,
563 sizeof (usr.ut_name), usr.ut_name);
564 #endif
565 } else
566 head = log_out(head, &usr);
567 break;
568 }
569 }
570 (void)fclose(fp);
571 usr.ut_time = time((time_t *)0);
572 (void)strcpy(usr.ut_line, "~");
573
574 if (Flags & AC_D) {
575 ltm = localtime(&usr.ut_time);
576 if (day >= 0 && day != ltm->tm_yday) {
577 /*
578 * print yesterday's total
579 */
580 secs = usr.ut_time;
581 secs -= ltm->tm_sec;
582 secs -= 60 * ltm->tm_min;
583 secs -= 3600 * ltm->tm_hour;
584 show_today(Users, head, secs);
585 }
586 }
587 /*
588 * anyone still logged in gets time up to now
589 */
590 head = log_out(head, &usr);
591
592 if (Flags & AC_D)
593 show_today(Users, head, time((time_t *)0));
594 else {
595 if (Flags & AC_P)
596 show_users(Users);
597 show("total", Total);
598 }
599 return 0;
600 }
601
602 static void
603 usage()
604 {
605
606 (void)fprintf(stderr,
607 "usage: %s [-d | -p] [-t tty] [-w wtmp] [users ...]\n",
608 getprogname());
609 exit(1);
610 }
611