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