ac.c revision 1.22 1 /* $NetBSD: ac.c,v 1.22 2006/05/23 01:16:33 christos 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.22 2006/05/23 01:16:33 christos 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 <time.h>
65 #include <utmp.h>
66 #include <ttyent.h>
67
68 /*
69 * this is for our list of currently logged in sessions
70 */
71 struct utmp_list {
72 struct utmp_list *next;
73 struct utmp usr;
74 };
75
76 /*
77 * this is for our list of users that are accumulating time.
78 */
79 struct user_list {
80 struct user_list *next;
81 char name[UT_NAMESIZE+1];
82 time_t secs;
83 };
84
85 /*
86 * this is for chosing whether to ignore a login
87 */
88 struct tty_list {
89 struct tty_list *next;
90 char name[UT_LINESIZE+3];
91 int len;
92 int ret;
93 };
94
95 /*
96 * globals - yes yuk
97 */
98 static time_t Total = 0;
99 static time_t FirstTime = 0;
100 static int Flags = 0;
101 static struct user_list *Users = NULL;
102 static struct tty_list *Ttys = NULL;
103 static int Maxcon = 0, Ncon = 0;
104 static char (*Con)[UT_LINESIZE] = NULL;
105
106 #define NEW(type) (type *)malloc(sizeof (type))
107
108 #define is_login_tty(line) \
109 (bsearch(line, Con, Ncon, sizeof(Con[0]), compare) != NULL)
110
111 #define AC_W 1 /* not _PATH_WTMP */
112 #define AC_D 2 /* daily totals (ignore -p) */
113 #define AC_P 4 /* per-user totals */
114 #define AC_U 8 /* specified users only */
115 #define AC_T 16 /* specified ttys only */
116
117 #ifdef DEBUG
118 static int Debug = 0;
119 #endif
120
121 static int ac(FILE *);
122 static struct tty_list *add_tty(char *);
123 static int do_tty(char *);
124 static FILE *file(const char *);
125 static struct utmp_list *log_in(struct utmp_list *, struct utmp *);
126 static struct utmp_list *log_out(struct utmp_list *, struct utmp *);
127 #ifdef notdef
128 static int on_console(struct utmp_list *);
129 #endif
130 static void find_login_ttys(void);
131 static void show(const char *, time_t);
132 static void show_today(struct user_list *, struct utmp_list *,
133 time_t);
134 static void show_users(struct user_list *);
135 static struct user_list *update_user(struct user_list *, char *, time_t);
136 static int compare(const void *, const void *);
137 static void usage(void);
138
139 /*
140 * open wtmp or die
141 */
142 static FILE *
143 file(const char *name)
144 {
145 FILE *fp;
146
147 if (strcmp(name, "-") == 0)
148 fp = stdin;
149 else if ((fp = fopen(name, "r")) == NULL)
150 err(1, "%s", name);
151 /* in case we want to discriminate */
152 if (strcmp(_PATH_WTMP, name))
153 Flags |= AC_W;
154 return fp;
155 }
156
157 static struct tty_list *
158 add_tty(char *name)
159 {
160 struct tty_list *tp;
161 char *rcp;
162
163 Flags |= AC_T;
164
165 if ((tp = NEW(struct tty_list)) == NULL)
166 err(1, "malloc");
167 tp->len = 0; /* full match */
168 tp->ret = 1; /* do if match */
169 if (*name == '!') { /* don't do if match */
170 tp->ret = 0;
171 name++;
172 }
173 (void)strlcpy(tp->name, name, sizeof (tp->name));
174 if ((rcp = strchr(tp->name, '*')) != NULL) { /* wild card */
175 *rcp = '\0';
176 tp->len = strlen(tp->name); /* match len bytes only */
177 }
178 tp->next = Ttys;
179 Ttys = tp;
180 return Ttys;
181 }
182
183 /*
184 * should we process the named tty?
185 */
186 static int
187 do_tty(char *name)
188 {
189 struct tty_list *tp;
190 int def_ret = 0;
191
192 for (tp = Ttys; tp != NULL; tp = tp->next) {
193 if (tp->ret == 0) /* specific don't */
194 def_ret = 1; /* default do */
195 if (tp->len != 0) {
196 if (strncmp(name, tp->name, tp->len) == 0)
197 return tp->ret;
198 } else {
199 if (strncmp(name, tp->name, sizeof (tp->name)) == 0)
200 return tp->ret;
201 }
202 }
203 return def_ret;
204 }
205
206 static int
207 compare(const void *a, const void *b)
208 {
209 return strncmp(a, b, UT_LINESIZE);
210 }
211
212 /*
213 * Deal correctly with multiple virtual consoles/login ttys.
214 * We read the ttyent's from /etc/ttys and classify as login
215 * ttys ones that are running getty and they are turned on.
216 */
217 static void
218 find_login_ttys(void)
219 {
220 struct ttyent *tty;
221 char (*nCon)[UT_LINESIZE];
222
223 if ((Con = malloc((Maxcon = 10) * sizeof(Con[0]))) == NULL)
224 err(1, "malloc");
225
226 setttyent();
227 while ((tty = getttyent()) != NULL)
228 if ((tty->ty_status & TTY_ON) != 0 &&
229 strstr(tty->ty_getty, "getty") != NULL) {
230 if (Ncon == Maxcon) {
231 if ((nCon = realloc(Con, (Maxcon + 10) *
232 sizeof(Con[0]))) == NULL)
233 err(1, "malloc");
234 Con = nCon;
235 Maxcon += 10;
236 }
237 (void)strncpy(Con[Ncon++], tty->ty_name, UT_LINESIZE);
238 }
239 endttyent();
240 qsort(Con, Ncon, sizeof(Con[0]), compare);
241 }
242
243 #ifdef notdef
244 /*
245 * is someone logged in on Console/login tty?
246 */
247 static int
248 on_console(struct utmp_list *head)
249 {
250 struct utmp_list *up;
251
252 for (up = head; up; up = up->next)
253 if (is_login_tty(up->usr.ut_line))
254 return 1;
255
256 return 0;
257 }
258 #endif
259
260 /*
261 * update user's login time
262 */
263 static struct user_list *
264 update_user(struct user_list *head, char *name, time_t secs)
265 {
266 struct user_list *up;
267
268 for (up = head; up != NULL; up = up->next) {
269 if (strncmp(up->name, name, sizeof (up->name) - 1) == 0) {
270 up->secs += secs;
271 Total += secs;
272 return head;
273 }
274 }
275 /*
276 * not found so add new user unless specified users only
277 */
278 if (Flags & AC_U)
279 return head;
280
281 if ((up = NEW(struct user_list)) == NULL)
282 err(1, "malloc");
283 up->next = head;
284 (void)strlcpy(up->name, name, sizeof (up->name));
285 up->secs = secs;
286 Total += secs;
287 return up;
288 }
289
290 int
291 main(int argc, char **argv)
292 {
293 FILE *fp;
294 int c;
295
296 fp = NULL;
297 while ((c = getopt(argc, argv, "Ddpt:w:")) != -1) {
298 switch (c) {
299 #ifdef DEBUG
300 case 'D':
301 Debug++;
302 break;
303 #endif
304 case 'd':
305 Flags |= AC_D;
306 break;
307 case 'p':
308 Flags |= AC_P;
309 break;
310 case 't': /* only do specified ttys */
311 add_tty(optarg);
312 break;
313 case 'w':
314 fp = file(optarg);
315 break;
316 case '?':
317 default:
318 usage();
319 }
320 }
321
322 find_login_ttys();
323
324 if (optind < argc) {
325 /*
326 * initialize user list
327 */
328 for (; optind < argc; optind++) {
329 Users = update_user(Users, argv[optind], 0L);
330 }
331 Flags |= AC_U; /* freeze user list */
332 }
333 if (Flags & AC_D)
334 Flags &= ~AC_P;
335 if (fp == NULL) {
336 /*
337 * if _PATH_WTMP does not exist, exit quietly
338 */
339 if (access(_PATH_WTMP, 0) != 0 && errno == ENOENT)
340 return 0;
341
342 fp = file(_PATH_WTMP);
343 }
344 ac(fp);
345
346 return 0;
347 }
348
349 /*
350 * print login time in decimal hours
351 */
352 static void
353 show(const char *name, time_t secs)
354 {
355 (void)printf("\t%-*s %8.2f\n", UT_NAMESIZE, name,
356 ((double)secs / 3600));
357 }
358
359 static void
360 show_users(struct user_list *list)
361 {
362 struct user_list *lp;
363
364 for (lp = list; lp; lp = lp->next)
365 show(lp->name, lp->secs);
366 }
367
368 /*
369 * print total login time for 24hr period in decimal hours
370 */
371 static void
372 show_today(struct user_list *users, struct utmp_list *logins, time_t secs)
373 {
374 struct user_list *up;
375 struct utmp_list *lp;
376 char date[64];
377 time_t yesterday = secs - 1;
378
379 (void)strftime(date, sizeof (date), "%b %e total",
380 localtime(&yesterday));
381
382 /* restore the missing second */
383 yesterday++;
384
385 for (lp = logins; lp != NULL; lp = lp->next) {
386 secs = yesterday - lp->usr.ut_time;
387 Users = update_user(Users, lp->usr.ut_name, secs);
388 lp->usr.ut_time = yesterday; /* as if they just logged in */
389 }
390 secs = 0;
391 for (up = users; up != NULL; up = up->next) {
392 secs += up->secs;
393 up->secs = 0; /* for next day */
394 }
395 if (secs)
396 (void)printf("%s %11.2f\n", date, ((double)secs / 3600));
397 }
398
399 /*
400 * log a user out and update their times.
401 * if ut_line is "~", we log all users out as the system has
402 * been shut down.
403 */
404 static struct utmp_list *
405 log_out(struct utmp_list *head, struct utmp *up)
406 {
407 struct utmp_list *lp, *lp2, *tlp;
408 time_t secs;
409
410 for (lp = head, lp2 = NULL; lp != NULL; )
411 if (*up->ut_line == '~' || strncmp(lp->usr.ut_line, up->ut_line,
412 sizeof (up->ut_line)) == 0) {
413 secs = up->ut_time - lp->usr.ut_time;
414 Users = update_user(Users, lp->usr.ut_name, secs);
415 #ifdef DEBUG
416 if (Debug)
417 printf("%-.*s %-.*s: %-.*s logged out (%2d:%02d:%02d)\n",
418 19, ctime(&up->ut_time),
419 sizeof (lp->usr.ut_line), lp->usr.ut_line,
420 sizeof (lp->usr.ut_name), lp->usr.ut_name,
421 secs / 3600, (secs % 3600) / 60, secs % 60);
422 #endif
423 /*
424 * now lose it
425 */
426 tlp = lp;
427 lp = lp->next;
428 if (tlp == head) {
429 head = lp;
430 if (tlp == head)
431 head = NULL;
432 }
433 else if (lp2 != NULL)
434 lp2->next = lp;
435 free(tlp);
436 } else {
437 lp2 = lp;
438 lp = lp->next;
439 }
440 return head;
441 }
442
443
444 /*
445 * if do_tty says ok, login a user
446 */
447 struct utmp_list *
448 log_in(struct utmp_list *head, struct utmp *up)
449 {
450 struct utmp_list *lp;
451
452 /*
453 * If we are doing specified ttys only, we ignore
454 * anything else.
455 */
456 if (Flags & AC_T)
457 if (!do_tty(up->ut_line))
458 return head;
459
460 /*
461 * go ahead and log them in
462 */
463 if ((lp = NEW(struct utmp_list)) == NULL)
464 err(1, "malloc");
465 lp->next = head;
466 head = lp;
467 memmove((char *)&lp->usr, (char *)up, sizeof (struct utmp));
468 #ifdef DEBUG
469 if (Debug) {
470 printf("%-.*s %-.*s: %-.*s logged in", 19,
471 ctime(&lp->usr.ut_time), sizeof (up->ut_line),
472 up->ut_line, sizeof (up->ut_name), up->ut_name);
473 if (*up->ut_host)
474 printf(" (%-.*s)", sizeof (up->ut_host), up->ut_host);
475 putchar('\n');
476 }
477 #endif
478 return head;
479 }
480
481 static int
482 ac(FILE *fp)
483 {
484 struct utmp_list *lp, *head = NULL;
485 struct utmp usr;
486 struct tm *ltm;
487 time_t secs = 0;
488 int day = -1;
489
490 while (fread((char *)&usr, sizeof(usr), 1, fp) == 1) {
491 if (!FirstTime)
492 FirstTime = usr.ut_time;
493 if (Flags & AC_D) {
494 ltm = localtime(&usr.ut_time);
495 if (day >= 0 && day != ltm->tm_yday) {
496 day = ltm->tm_yday;
497 /*
498 * print yesterday's total
499 */
500 secs = usr.ut_time;
501 secs -= ltm->tm_sec;
502 secs -= 60 * ltm->tm_min;
503 secs -= 3600 * ltm->tm_hour;
504 show_today(Users, head, secs);
505 } else
506 day = ltm->tm_yday;
507 }
508 switch(*usr.ut_line) {
509 case '|':
510 secs = usr.ut_time;
511 break;
512 case '{':
513 secs -= usr.ut_time;
514 /*
515 * adjust time for those logged in
516 */
517 for (lp = head; lp != NULL; lp = lp->next)
518 lp->usr.ut_time -= secs;
519 break;
520 case '~': /* reboot or shutdown */
521 head = log_out(head, &usr);
522 FirstTime = usr.ut_time; /* shouldn't be needed */
523 break;
524 default:
525 /*
526 * if they came in on tty[p-y]*, then it is only
527 * a login session if the ut_host field is non-empty,
528 * or this tty is a login tty [eg. a console]
529 */
530 if (*usr.ut_name) {
531 if ((strncmp(usr.ut_line, "tty", 3) != 0 &&
532 strncmp(usr.ut_line, "dty", 3) != 0) ||
533 strchr("pqrstuvwxyzPQRST", usr.ut_line[3]) == 0 ||
534 *usr.ut_host != '\0' ||
535 is_login_tty(usr.ut_line))
536 head = log_in(head, &usr);
537 #ifdef DEBUG
538 else if (Debug)
539 printf("%-.*s %-.*s: %-.*s ignored\n",
540 19, ctime(&usr.ut_time),
541 sizeof (usr.ut_line), usr.ut_line,
542 sizeof (usr.ut_name), usr.ut_name);
543 #endif
544 } else
545 head = log_out(head, &usr);
546 break;
547 }
548 }
549 (void)fclose(fp);
550 usr.ut_time = time((time_t *)0);
551 (void)strcpy(usr.ut_line, "~");
552
553 if (Flags & AC_D) {
554 ltm = localtime(&usr.ut_time);
555 if (day >= 0 && day != ltm->tm_yday) {
556 /*
557 * print yesterday's total
558 */
559 secs = usr.ut_time;
560 secs -= ltm->tm_sec;
561 secs -= 60 * ltm->tm_min;
562 secs -= 3600 * ltm->tm_hour;
563 show_today(Users, head, secs);
564 }
565 }
566 /*
567 * anyone still logged in gets time up to now
568 */
569 head = log_out(head, &usr);
570
571 if (Flags & AC_D)
572 show_today(Users, head, time((time_t *)0));
573 else {
574 if (Flags & AC_P)
575 show_users(Users);
576 show("total", Total);
577 }
578 return 0;
579 }
580
581 static void
582 usage(void)
583 {
584
585 (void)fprintf(stderr,
586 "usage: %s [-d | -p] [-t tty] [-w wtmp] [users ...]\n",
587 getprogname());
588 exit(1);
589 }
590