Generally use NULL instead of explicitly casting 0 to some pointer type.
[dragonfly.git] / usr.sbin / ac / ac.c
1 /*
2  *      Copyright (c) 1994 Christopher G. Demetriou.
3  *      @(#)Copyright (c) 1994, Simon J. Gerraty.
4  *
5  *      This is free software.  It comes with NO WARRANTY.
6  *      Permission to use, modify and distribute this source code
7  *      is granted subject to the following conditions.
8  *      1/ that the above copyright notice and this notice
9  *      are preserved in all copies and that due credit be given
10  *      to the author.
11  *      2/ that any changes to this code are clearly commented
12  *      as such so that the author does not get blamed for bugs
13  *      other than his own.
14  *
15  * $FreeBSD: src/usr.sbin/ac/ac.c,v 1.14.2.2 2002/03/12 19:55:04 phantom Exp $
16  * $DragonFly: src/usr.sbin/ac/ac.c,v 1.6 2004/03/20 17:46:47 cpressey Exp $
17  */
18
19 #include <sys/types.h>
20 #include <sys/file.h>
21 #include <sys/time.h>
22 #include <err.h>
23 #include <errno.h>
24 #include <langinfo.h>
25 #include <locale.h>
26 #include <pwd.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <utmp.h>
32
33 /*
34  * this is for our list of currently logged in sessions
35  */
36 struct utmp_list {
37         struct utmp_list *next;
38         struct utmp usr;
39 };
40
41 /*
42  * this is for our list of users that are accumulating time.
43  */
44 struct user_list {
45         struct user_list *next;
46         char    name[UT_NAMESIZE+1];
47         time_t  secs;
48 };
49
50 /*
51  * this is for chosing whether to ignore a login
52  */
53 struct tty_list {
54         struct tty_list *next;
55         char    name[UT_LINESIZE+3];
56         int     len;
57         int     ret;
58 };
59
60 /*
61  * globals - yes yuk
62  */
63 #ifdef CONSOLE_TTY
64 static char     *Console = CONSOLE_TTY;
65 #endif
66 static time_t   Total = 0;
67 static time_t   FirstTime = 0;
68 static int      Flags = 0;
69 static struct user_list *Users = NULL;
70 static struct tty_list *Ttys = NULL;
71
72 #define NEW(type) (type *)malloc(sizeof(type))
73
74 #define AC_W    1                               /* not _PATH_WTMP */
75 #define AC_D    2                               /* daily totals (ignore -p) */
76 #define AC_P    4                               /* per-user totals */
77 #define AC_U    8                               /* specified users only */
78 #define AC_T    16                              /* specified ttys only */
79
80 #ifdef DEBUG
81 static int Debug = 0;
82 #endif
83
84 int                     main(int, char **);
85 int                     ac(FILE *);
86 struct tty_list         *add_tty(char *);
87 int                     do_tty(char *);
88 FILE                    *file(char *);
89 struct utmp_list        *log_in(struct utmp_list *, struct utmp *);
90 struct utmp_list        *log_out(struct utmp_list *, struct utmp *);
91 int                     on_console(struct utmp_list *);
92 void                    show(char *, time_t);
93 void                    show_today(struct user_list *, struct utmp_list *,
94                             time_t);
95 void                    show_users(struct user_list *);
96 struct user_list        *update_user(struct user_list *, char *, time_t);
97 void                    usage(void);
98
99 /*
100  * open wtmp or die
101  */
102 FILE *
103 file(char *name)
104 {
105         FILE *fp;
106
107         if ((fp = fopen(name, "r")) == NULL)
108                 err(1, "%s", name);
109         /* in case we want to discriminate */
110         if (strcmp(_PATH_WTMP, name))
111                 Flags |= AC_W;
112         return fp;
113 }
114
115 struct tty_list *
116 add_tty(char *name)
117 {
118         struct tty_list *tp;
119         char *rcp;
120
121         Flags |= AC_T;
122
123         if ((tp = NEW(struct tty_list)) == NULL)
124                 errx(1, "malloc failed");
125         tp->len = 0;                            /* full match */
126         tp->ret = 1;                            /* do if match */
127         if (*name == '!') {                     /* don't do if match */
128                 tp->ret = 0;
129                 name++;
130         }
131         strncpy(tp->name, name, sizeof(tp->name) - 1);
132         tp->name[sizeof(tp->name) - 1] = '\0';
133         if ((rcp = strchr(tp->name, '*')) != NULL) {    /* wild card */
134                 *rcp = '\0';
135                 tp->len = strlen(tp->name);     /* match len bytes only */
136         }
137         tp->next = Ttys;
138         Ttys = tp;
139         return Ttys;
140 }
141
142 /*
143  * should we process the named tty?
144  */
145 int
146 do_tty(char *name)
147 {
148         struct tty_list *tp;
149         int def_ret = 0;
150
151         for (tp = Ttys; tp != NULL; tp = tp->next) {
152                 if (tp->ret == 0)               /* specific don't */
153                         def_ret = 1;            /* default do */
154                 if (tp->len != 0) {
155                         if (strncmp(name, tp->name, tp->len) == 0)
156                                 return tp->ret;
157                 } else {
158                         if (strncmp(name, tp->name, sizeof(tp->name)) == 0)
159                                 return tp->ret;
160                 }
161         }
162         return def_ret;
163 }
164
165 #ifdef CONSOLE_TTY
166 /*
167  * is someone logged in on Console?
168  */
169 int
170 on_console(struct utmp_list *head)
171 {
172         struct utmp_list *up;
173
174         for (up = head; up; up = up->next) {
175                 if (strncmp(up->usr.ut_line, Console,
176                     sizeof(up->usr.ut_line)) == 0)
177                         return 1;
178         }
179         return 0;
180 }
181 #endif
182
183 /*
184  * update user's login time
185  */
186 struct user_list *
187 update_user(struct user_list *head, char *name, time_t secs)
188 {
189         struct user_list *up;
190
191         for (up = head; up != NULL; up = up->next) {
192                 if (strncmp(up->name, name, UT_NAMESIZE) == 0) {
193                         up->secs += secs;
194                         Total += secs;
195                         return head;
196                 }
197         }
198         /*
199          * not found so add new user unless specified users only
200          */
201         if (Flags & AC_U)
202                 return head;
203
204         if ((up = NEW(struct user_list)) == NULL)
205                 errx(1, "malloc failed");
206         up->next = head;
207         strncpy(up->name, name, sizeof(up->name) - 1);
208         up->name[sizeof(up->name) - 1] = '\0';  /* paranoid! */
209         up->secs = secs;
210         Total += secs;
211         return up;
212 }
213
214 int
215 main(int argc, char **argv)
216 {
217         FILE *fp;
218         int c;
219
220         setlocale(LC_TIME, "");
221
222         fp = NULL;
223         while ((c = getopt(argc, argv, "Dc:dpt:w:")) != -1) {
224                 switch (c) {
225 #ifdef DEBUG
226                 case 'D':
227                         Debug++;
228                         break;
229 #endif
230                 case 'c':
231 #ifdef CONSOLE_TTY
232                         Console = optarg;
233 #else
234                         usage();                /* XXX */
235 #endif
236                         break;
237                 case 'd':
238                         Flags |= AC_D;
239                         break;
240                 case 'p':
241                         Flags |= AC_P;
242                         break;
243                 case 't':                       /* only do specified ttys */
244                         add_tty(optarg);
245                         break;
246                 case 'w':
247                         fp = file(optarg);
248                         break;
249                 case '?':
250                 default:
251                         usage();
252                         break;
253                 }
254         }
255         if (optind < argc) {
256                 /*
257                  * initialize user list
258                  */
259                 for (; optind < argc; optind++) {
260                         Users = update_user(Users, argv[optind], 0L);
261                 }
262                 Flags |= AC_U;                  /* freeze user list */
263         }
264         if (Flags & AC_D)
265                 Flags &= ~AC_P;
266         if (fp == NULL) {
267                 /*
268                  * if _PATH_WTMP does not exist, exit quietly
269                  */
270                 if (access(_PATH_WTMP, 0) != 0 && errno == ENOENT)
271                         return 0;
272
273                 fp = file(_PATH_WTMP);
274         }
275         ac(fp);
276
277         return 0;
278 }
279
280 /*
281  * print login time in decimal hours
282  */
283 void
284 show(char *name, time_t secs)
285 {
286         printf("\t%-*s %8.2f\n", UT_NAMESIZE, name,
287             ((double)secs / 3600));
288 }
289
290 void
291 show_users(struct user_list *list)
292 {
293         struct user_list *lp;
294
295         for (lp = list; lp; lp = lp->next)
296                 show(lp->name, lp->secs);
297 }
298
299 /*
300  * print total login time for 24hr period in decimal hours
301  */
302 void
303 show_today(struct user_list *users, struct utmp_list *logins, time_t secs)
304 {
305         struct user_list *up;
306         struct utmp_list *lp;
307         char date[64];
308         time_t yesterday = secs - 1;
309         static int d_first = -1;
310
311         if (d_first < 0)
312                 d_first = (*nl_langinfo(D_MD_ORDER) == 'd');
313         strftime(date, sizeof(date),
314                        d_first ? "%e %b  total" : "%b %e  total",
315                        localtime(&yesterday));
316
317         /* restore the missing second */
318         yesterday++;
319
320         for (lp = logins; lp != NULL; lp = lp->next) {
321                 secs = yesterday - lp->usr.ut_time;
322                 Users = update_user(Users, lp->usr.ut_name, secs);
323                 lp->usr.ut_time = yesterday;    /* as if they just logged in */
324         }
325         secs = 0;
326         for (up = users; up != NULL; up = up->next) {
327                 secs += up->secs;
328                 up->secs = 0;                   /* for next day */
329         }
330         if (secs)
331                 printf("%s %11.2f\n", date, ((double)secs / 3600));
332 }
333
334 /*
335  * log a user out and update their times.
336  * if ut_line is "~", we log all users out as the system has
337  * been shut down.
338  */
339 struct utmp_list *
340 log_out(struct utmp_list *head, struct utmp *up)
341 {
342         struct utmp_list *lp, *lp2, *tlp;
343         time_t secs;
344
345         for (lp = head, lp2 = NULL; lp != NULL; )
346                 if (*up->ut_line == '~' || strncmp(lp->usr.ut_line, up->ut_line,
347                     sizeof(up->ut_line)) == 0) {
348                         secs = up->ut_time - lp->usr.ut_time;
349                         Users = update_user(Users, lp->usr.ut_name, secs);
350 #ifdef DEBUG
351                         if (Debug)
352                                 printf("%-.*s %-.*s: %-.*s logged out (%2d:%02d:%02d)\n",
353                                     19, ctime(&up->ut_time),
354                                     sizeof(lp->usr.ut_line), lp->usr.ut_line,
355                                     sizeof(lp->usr.ut_name), lp->usr.ut_name,
356                                     secs / 3600, (secs % 3600) / 60, secs % 60);
357 #endif
358                         /*
359                          * now lose it
360                          */
361                         tlp = lp;
362                         lp = lp->next;
363                         if (tlp == head)
364                                 head = lp;
365                         else if (lp2 != NULL)
366                                 lp2->next = lp;
367                         free(tlp);
368                 } else {
369                         lp2 = lp;
370                         lp = lp->next;
371                 }
372         return head;
373 }
374
375
376 /*
377  * if do_tty says ok, login a user
378  */
379 struct utmp_list *
380 log_in(struct utmp_list *head, struct utmp *up)
381 {
382         struct utmp_list *lp;
383
384         /*
385          * this could be a login. if we're not dealing with
386          * the console name, say it is.
387          *
388          * If we are, and if ut_host==":0.0" we know that it
389          * isn't a real login. _But_ if we have not yet recorded
390          * someone being logged in on Console - due to the wtmp
391          * file starting after they logged in, we'll pretend they
392          * logged in, at the start of the wtmp file.
393          */
394
395 #ifdef CONSOLE_TTY
396         if (up->ut_host[0] == ':') {
397                 /*
398                  * SunOS 4.0.2 does not treat ":0.0" as special but we
399                  * do.
400                  */
401                 if (on_console(head))
402                         return head;
403                 /*
404                  * ok, no recorded login, so they were here when wtmp
405                  * started!  Adjust ut_time!
406                  */
407                 up->ut_time = FirstTime;
408                 /*
409                  * this allows us to pick the right logout
410                  */
411                 strncpy(up->ut_line, Console, sizeof(up->ut_line) - 1);
412                 up->ut_line[sizeof(up->ut_line) - 1] = '\0'; /* paranoid! */
413         }
414 #endif
415         /*
416          * If we are doing specified ttys only, we ignore
417          * anything else.
418          */
419         if (Flags & AC_T)
420                 if (!do_tty(up->ut_line))
421                         return head;
422
423         /*
424          * go ahead and log them in
425          */
426         if ((lp = NEW(struct utmp_list)) == NULL)
427                 errx(1, "malloc failed");
428         lp->next = head;
429         head = lp;
430         memmove((char *)&lp->usr, (char *)up, sizeof(struct utmp));
431 #ifdef DEBUG
432         if (Debug) {
433                 printf("%-.*s %-.*s: %-.*s logged in", 19,
434                     ctime(&lp->usr.ut_time), sizeof(up->ut_line),
435                        up->ut_line, sizeof(up->ut_name), up->ut_name);
436                 if (*up->ut_host)
437                         printf(" (%-.*s)", sizeof(up->ut_host), up->ut_host);
438                 putchar('\n');
439         }
440 #endif
441         return head;
442 }
443
444 int
445 ac(FILE *fp)
446 {
447         struct utmp_list *lp, *head = NULL;
448         struct utmp usr;
449         struct tm *ltm;
450         time_t secs;
451         int day = -1;
452
453         while (fread((char *)&usr, sizeof(usr), 1, fp) == 1) {
454                 if (!FirstTime)
455                         FirstTime = usr.ut_time;
456                 if (Flags & AC_D) {
457                         ltm = localtime(&usr.ut_time);
458                         if (day >= 0 && day != ltm->tm_yday) {
459                                 day = ltm->tm_yday;
460                                 /*
461                                  * print yesterday's total
462                                  */
463                                 secs = usr.ut_time;
464                                 secs -= ltm->tm_sec;
465                                 secs -= 60 * ltm->tm_min;
466                                 secs -= 3600 * ltm->tm_hour;
467                                 show_today(Users, head, secs);
468                         } else
469                                 day = ltm->tm_yday;
470                 }
471                 switch(*usr.ut_line) {
472                 case '|':
473                         secs = usr.ut_time;
474                         break;
475                 case '{':
476                         secs -= usr.ut_time;
477                         /*
478                          * adjust time for those logged in
479                          */
480                         for (lp = head; lp != NULL; lp = lp->next)
481                                 lp->usr.ut_time -= secs;
482                         break;
483                 case '~':                       /* reboot or shutdown */
484                         head = log_out(head, &usr);
485                         FirstTime = usr.ut_time; /* shouldn't be needed */
486                         break;
487                 default:
488                         /*
489                          * if they came in on tty[p-sP-S]*, then it is only
490                          * a login session if the ut_host field is non-empty
491                          */
492                         if (*usr.ut_name) {
493                                 if (strncmp(usr.ut_line, "tty", 3) != 0 ||
494                                     strchr("pqrsPQRS", usr.ut_line[3]) == 0 ||
495                                     *usr.ut_host != '\0')
496                                         head = log_in(head, &usr);
497                         } else
498                                 head = log_out(head, &usr);
499                         break;
500                 }
501         }
502         fclose(fp);
503         if (!(Flags & AC_W))
504                 usr.ut_time = time(NULL);
505         strcpy(usr.ut_line, "~");
506
507         if (Flags & AC_D) {
508                 ltm = localtime(&usr.ut_time);
509                 if (day >= 0 && day != ltm->tm_yday) {
510                         /*
511                          * print yesterday's total
512                          */
513                         secs = usr.ut_time;
514                         secs -= ltm->tm_sec;
515                         secs -= 60 * ltm->tm_min;
516                         secs -= 3600 * ltm->tm_hour;
517                         show_today(Users, head, secs);
518                 }
519         }
520         /*
521          * anyone still logged in gets time up to now
522          */
523         head = log_out(head, &usr);
524
525         if (Flags & AC_D)
526                 show_today(Users, head, time(NULL));
527         else {
528                 if (Flags & AC_P)
529                         show_users(Users);
530                 show("total", Total);
531         }
532         return 0;
533 }
534
535 void
536 usage(void)
537 {
538         fprintf(stderr,
539 #ifdef CONSOLE_TTY
540             "ac [-dp] [-c console] [-t tty] [-w wtmp] [users ...]\n");
541 #else
542             "ac [-dp] [-t tty] [-w wtmp] [users ...]\n");
543 #endif
544         exit(1);
545 }